Compare commits

...
18 changed files with 1216 additions and 142 deletions

No files matched your search

+8
View File
@@ -1671,6 +1671,14 @@ ifeq ($(HAVE_SDL3), 1)
input/drivers/sdl3_input.o \
gfx/drivers/sdl3_gfx.o \
gfx/common/sdl3_common.o
ifneq ($(filter 1,$(HAVE_OPENGL) $(HAVE_OPENGL1) $(HAVE_OPENGL_CORE) $(HAVE_OPENGLES)),)
OBJ += gfx/drivers_context/sdl3_gl_ctx.o
endif
ifeq ($(HAVE_VULKAN), 1)
OBJ += gfx/drivers_context/sdl3_vk_ctx.o
endif
else ifeq ($(HAVE_SDL2), 1)
HAVE_SDL_COMMON = 1
OBJ += gfx/drivers/sdl2_gfx.o \
+363 -10
View File
@@ -27,20 +27,73 @@
#include <SDL3/SDL.h>
#include "sdl3_common.h"
#include "../../configuration.h"
#include "../../input/input_driver.h"
#include "../../retroarch.h"
/* SDL3 dropped SDL_GetWindowWMInfo / SDL_SysWMinfo in favour of the
* window properties API, so the native display/window handles are read
* from SDL_GetWindowProperties() instead.
*
* The display type is derived from SDL's active video driver name, so
* no platform #ifdefs are needed at the call site; each case below
* compiles away when the corresponding backend isn't built in, making
* unsupported types safe no-ops. */
/* Keeps track of the window position to allow video_window_save_positions. */
static void sdl3_window_save_position(SDL_Window *win)
{
int x, y, w, h;
settings_t *settings = config_get_ptr();
if (!win || !settings || !settings->bools.video_window_save_positions)
return;
if (SDL_GetWindowFlags(win) & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED))
return;
if (!SDL_GetWindowPosition(win, &x, &y) || !SDL_GetWindowSize(win, &w, &h)
|| w <= 0 || h <= 0)
return;
settings->uints.window_position_x = (unsigned)x;
settings->uints.window_position_y = (unsigned)y;
settings->uints.window_position_width = (unsigned)w;
settings->uints.window_position_height = (unsigned)h;
}
void sdl3_pump_window_events(bool *quit, bool *resize)
{
SDL_Event event;
SDL_PumpEvents();
/* Only consume quit and window events here. The SDL3 input driver
* handles the keyboard/mouse events within the same queue. */
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_QUIT, SDL_EVENT_QUIT) > 0)
*quit = true;
/* Check if the display size was changed. */
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_WINDOW_FIRST, SDL_EVENT_WINDOW_LAST) > 0)
{
if (event.type == SDL_EVENT_WINDOW_RESIZED || event.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED)
*resize = true;
if (event.type == SDL_EVENT_WINDOW_RESIZED || event.type == SDL_EVENT_WINDOW_MOVED)
sdl3_window_save_position(SDL_GetWindowFromID(event.window.windowID));
}
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_DISPLAY_FIRST, SDL_EVENT_DISPLAY_LAST) > 0)
{
if (event.type == SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED)
*resize = true;
}
/* Clear out the input queue if we're not using the
* SDL driver. */
if (input_state_get_ptr()->current_driver != &input_sdl3)
{
SDL_FlushEvents(SDL_EVENT_KEY_DOWN, SDL_EVENT_MOUSE_REMOVED);
SDL_FlushEvents(SDL_EVENT_FINGER_DOWN, SDL_EVENT_FINGER_CANCELED);
SDL_FlushEvents(SDL_EVENT_PEN_PROXIMITY_IN, SDL_EVENT_PEN_AXIS);
}
}
void sdl3_set_handles(SDL_Window *window)
{
SDL_PropertiesID props = SDL_GetWindowProperties(window);
const char *sdl_driver = SDL_GetCurrentVideoDriver();
SDL_PropertiesID props = SDL_GetWindowProperties(window);
const char *sdl_driver = SDL_GetCurrentVideoDriver();
enum rarch_display_type display_type = RARCH_DISPLAY_NONE;
if (!props)
@@ -110,3 +163,303 @@ void sdl3_set_handles(SDL_Window *window)
break;
}
}
/* Moves the window to the provided 1-based monitor. If 0
* is provided, leave as is. */
static void sdl3_window_move_to_monitor(SDL_Window *win,
unsigned monitor_index)
{
int count;
SDL_DisplayID *displays;
SDL_DisplayID display = 0;
if (!win || monitor_index == 0)
return;
displays = SDL_GetDisplays(&count);
if (displays && (int)monitor_index <= count)
display = displays[monitor_index - 1];
SDL_free(displays);
/* Out-of-range index (e.g. the configured monitor was unplugged):
* leave the window where the system placed it. */
if (display == 0)
return;
SDL_SetWindowPosition(win,
SDL_WINDOWPOS_CENTERED_DISPLAY(display),
SDL_WINDOWPOS_CENTERED_DISPLAY(display));
SDL_SyncWindow(win);
}
/* Creates the SDL3 window. */
static SDL_Window *sdl3_window_create(unsigned width, unsigned height,
bool fullscreen, SDL_WindowFlags backend_flags)
{
SDL_Window *win;
settings_t *settings = config_get_ptr();
SDL_WindowFlags flags = backend_flags
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_HIGH_PIXEL_DENSITY;
if (fullscreen)
flags |= SDL_WINDOW_FULLSCREEN;
if (!settings->bools.video_window_show_decorations)
flags |= SDL_WINDOW_BORDERLESS;
if (!(win = SDL_CreateWindow("RetroArch", width, height, flags)))
return NULL;
/* Set either the window position, or the active monitor. This is ignored
* in Wayland where windows are not manually positioned. */
if (settings->bools.video_window_save_positions
&& !fullscreen
&& settings->uints.window_position_width
&& settings->uints.window_position_height
&& !string_is_equal(SDL_GetCurrentVideoDriver(), "wayland"))
{
SDL_SetWindowPosition(win,
(int)settings->uints.window_position_x,
(int)settings->uints.window_position_y);
SDL_SyncWindow(win);
}
else
sdl3_window_move_to_monitor(win, settings->uints.video_monitor_index);
/* SDL_EVENT_TEXT_INPUT is emitted for windows that opted in for
* it. The SDL3 input driver handles those events for menu
* text entry and core keyboard callbacks. */
SDL_StartTextInput(win);
return win;
}
/* Applies the fullscreen state. */
static void sdl3_window_apply_fullscreen(SDL_Window *win,
unsigned width, unsigned height, bool fullscreen)
{
SDL_DisplayMode mode;
settings_t *settings = config_get_ptr();
bool exclusive = fullscreen
&& !settings->bools.video_windowed_fullscreen
&& SDL_GetClosestFullscreenDisplayMode(
SDL_GetDisplayForWindow(win),
width, height,
settings->floats.video_refresh_rate, false, &mode);
SDL_SetWindowFullscreenMode(win, exclusive ? &mode : NULL);
SDL_SetWindowFullscreen(win, fullscreen);
if (fullscreen)
SDL_HideCursor();
else
{
/* Leaving fullscreen is asynchronous, and size requests
* are dropped while still fullscreen, so we wait until
* it has completed. */
SDL_SyncWindow(win);
SDL_SetWindowSize(win, width, height);
SDL_ShowCursor();
}
}
bool sdl3_window_set_video_mode(SDL_Window **win,
unsigned width, unsigned height, bool fullscreen,
SDL_WindowFlags backend_flags)
{
if (*win)
SDL_SetWindowBordered(*win, config_get_ptr()->bools.video_window_show_decorations);
else if (!(*win = sdl3_window_create(width, height, fullscreen, backend_flags)))
return false;
sdl3_window_apply_fullscreen(*win, width, height, fullscreen);
sdl3_set_handles(*win);
return true;
}
void sdl3_window_get_video_size(SDL_Window *win,
unsigned *width, unsigned *height)
{
const SDL_DisplayMode *mode;
if (win)
{
int w, h;
SDL_GetWindowSizeInPixels(win, &w, &h);
*width = w;
*height = h;
return;
}
/* The window doesn't exist yet, so report the desktop size. */
mode = SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay());
if (mode)
{
*width = mode->w;
*height = mode->h;
}
}
float sdl3_window_get_refresh_rate(SDL_Window *win)
{
const SDL_DisplayMode *mode;
if (!win)
return 0.0f;
mode = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(win));
return mode ? mode->refresh_rate : 0.0f;
}
void sdl3_window_update_title(SDL_Window *win)
{
char title[128];
if (!win)
return;
title[0] = '\0';
video_driver_get_window_title(title, sizeof(title));
if (title[0])
SDL_SetWindowTitle(win, title);
}
bool sdl3_window_has_focus(SDL_Window *win)
{
if (!win)
return false;
/* This only checks for keyboard focus, as SDL_WINDOW_MOUSE_FOCUS will
* pause when the mouse leaves the window too. */
return (SDL_GetWindowFlags(win) & SDL_WINDOW_INPUT_FOCUS) != 0;
}
bool sdl3_suppress_screensaver(void *data, bool enable)
{
return enable ? SDL_DisableScreenSaver() : SDL_EnableScreenSaver();
}
/* Returns the configured input driver if it works alongside an SDL3
* window, or NULL if the SDL3 input driver should be used instead.
*
* The udev, linuxraw, raw and dinput input drivers read input devices
* directly, so they will function inside an SDL3 window. Drivers tied
* to a windowing system like x, wayland, or cocoa however, should end
* up using the SDL3 input driver. */
static input_driver_t *sdl3_passthrough_input(const char *ident)
{
#ifdef HAVE_UDEV
if (string_is_equal(ident, "udev"))
return &input_udev;
#endif
#if defined(__linux__) && !defined(ANDROID)
if (string_is_equal(ident, "linuxraw"))
return &input_linuxraw;
#endif
#if defined(_WIN32) && !defined(_XBOX) && _WIN32_WINNT >= 0x0501 && !defined(__WINRT__)
#ifdef HAVE_WINRAWINPUT
if (string_is_equal(ident, "raw"))
return &input_winraw;
#endif
#endif
#ifdef HAVE_DINPUT
if (string_is_equal(ident, "dinput"))
return &input_dinput;
#endif
return NULL;
}
void sdl3_input_driver(const char *joypad_name,
input_driver_t **input, void **input_data)
{
input_driver_t *passthrough = sdl3_passthrough_input(config_get_ptr()->arrays.input_driver);
if (passthrough)
{
if ((*input_data = input_driver_init_wrap(passthrough, joypad_name)))
{
*input = passthrough;
return;
}
/* The configured input driver failed, so fallback to SDL3. */
}
/* Use the SDL3 input driver, taking advantage of the window's event queue. */
*input_data = input_driver_init_wrap(&input_sdl3, joypad_name);
*input = *input_data ? &input_sdl3 : NULL;
}
void sdl3_ctx_input_driver(void *data,
const char *name,
input_driver_t **input, void **input_data)
{
sdl3_input_driver(name, input, input_data);
}
bool sdl3_ctx_enabled(const char *ctx_ident)
{
settings_t *settings = config_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
/* Determine the user's desired video driver. When a core requests
* gl/glcore/vulkan, the user's original selection is stashed in
* cached_driver_id. Use that if it's available. */
const char *video_ident = video_st->cached_driver_id[0]
? video_st->cached_driver_id
: settings->arrays.video_driver;
/* Keep the hardware rendering within the SDL3 window. When SDL3 isn't
* selected, will pass off to the next native context instead. */
return string_is_equal(video_ident, "sdl3")
|| string_is_equal(settings->arrays.video_context_driver, ctx_ident);
}
/* Shared ctx callbacks only need the SDL_Window*, which
* every SDL3 driver data struct keeps as its first member. */
static SDL_Window *sdl3_ctx_window(void *data)
{
return data ? *(SDL_Window**)data : NULL;
}
void sdl3_ctx_get_video_size(void *data, unsigned *width, unsigned *height)
{
if (data)
sdl3_window_get_video_size(sdl3_ctx_window(data), width, height);
}
float sdl3_ctx_get_refresh_rate(void *data)
{
return sdl3_window_get_refresh_rate(sdl3_ctx_window(data));
}
void sdl3_ctx_update_title(void *data)
{
sdl3_window_update_title(sdl3_ctx_window(data));
}
bool sdl3_ctx_has_focus(void *data)
{
return sdl3_window_has_focus(sdl3_ctx_window(data));
}
void sdl3_ctx_check_window(void *data, bool *quit, bool *resize,
unsigned *width, unsigned *height)
{
SDL_Window *win = sdl3_ctx_window(data);
sdl3_pump_window_events(quit, resize);
if (*resize && win)
sdl3_window_get_video_size(win, width, height);
}
void sdl3_show_mouse(void *data, bool state)
{
if (state)
SDL_ShowCursor();
else
SDL_HideCursor();
}
+59 -1
View File
@@ -44,6 +44,8 @@ typedef struct sdl3_tex
typedef struct _sdl3_video
{
SDL_Window *window; /* Must be first because it's shared across
* sdl3_ctx_* callbacks. */
double rotation;
struct video_viewport vp;
@@ -52,12 +54,68 @@ typedef struct _sdl3_video
sdl3_tex_t frame; /* ptr alignment */
sdl3_tex_t menu; /* ptr alignment */
SDL_Window *window;
SDL_Renderer *renderer;
uint8_t flags;
} sdl3_video_t;
/* Sets the window's native display/window handles (X11, Wayland,
* Win32, Cocoa, KMS) to the video driver state via SDL's window
* properties API, so subsystems that require the raw handles
* can get access to them. */
void sdl3_set_handles(SDL_Window *window);
/* Processes the SDL event queue, and cleans up the ones that
* are handled elsewhere. For example: quit / window / display
* events, etc. The keyboard/mouse events are left in the queue
* for the SDL input driver to handle itself. */
void sdl3_pump_window_events(bool *quit, bool *resize);
/* Creates or resizes the window, or toggles fullscreen. */
bool sdl3_window_set_video_mode(SDL_Window **win,
unsigned width, unsigned height, bool fullscreen,
SDL_WindowFlags backend_flags);
/* Retrieves the window size in pixels, or the desktop mode when the
* window doesn't exist yet. */
void sdl3_window_get_video_size(SDL_Window *win,
unsigned *width, unsigned *height);
/* Get the refresh rate of the display the window is on, in Hz.
* Returns 0.0f when there is no window or when we can't tell. */
float sdl3_window_get_refresh_rate(SDL_Window *win);
/* Updates the window title to what is expected. */
void sdl3_window_update_title(SDL_Window *win);
/* True when the window has keyboard input focus. */
bool sdl3_window_has_focus(SDL_Window *win);
/* Toggles screensaver inhibition. */
bool sdl3_suppress_screensaver(void *data, bool enable);
/* Initializes the input driver paired with an SDL3 window. */
void sdl3_input_driver(const char *joypad_name,
input_driver_t **input, void **input_data);
/* gfx_ctx_driver_t input_driver callback wrapping sdl3_input_driver. */
void sdl3_ctx_input_driver(void *data, const char *name,
input_driver_t **input, void **input_data);
/* Determines whether or not an SDL3 context driver should be
* initialized. Will return true when the user is using the
* sdl3 video driver, or when ctx_ident is explicitly configured
* as the context driver. */
bool sdl3_ctx_enabled(const char *ctx_ident);
void sdl3_ctx_get_video_size(void *data, unsigned *width, unsigned *height);
float sdl3_ctx_get_refresh_rate(void *data);
void sdl3_ctx_update_title(void *data);
bool sdl3_ctx_has_focus(void *data);
void sdl3_ctx_check_window(void *data, bool *quit, bool *resize,
unsigned *width, unsigned *height);
/* Shows or hides the mouse cursor. */
void sdl3_show_mouse(void *data, bool state);
#endif
+48 -7
View File
@@ -36,6 +36,13 @@
#include "vksym.h"
#include <libretro_vulkan.h>
#ifdef HAVE_SDL3
/* Must come after the Vulkan headers so SDL_vulkan.h picks up the
* real Vk* types instead of forward-declaring its own. */
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#endif
#include "../../verbosity.h"
#include "../../configuration.h"
@@ -1071,12 +1078,13 @@ static VkInstance vulkan_context_create_instance_wrapper(void *opaque, const VkI
gfx_ctx_vulkan_data_t *vk = (gfx_ctx_vulkan_data_t *)opaque;
VkInstanceCreateInfo info = *create_info;
VkInstance instance = VK_NULL_HANDLE;
const char **instance_extensions = (const char**)malloc((info.enabledExtensionCount + 3
+ ARRAY_SIZE(vulkan_optional_device_extensions)) * sizeof(const char *));
const char **instance_layers = (const char**)malloc((info.enabledLayerCount + 1) * sizeof(const char *));
const char *required_extensions[3];
/* Room for VK_KHR_surface, WSI, SDL3, and the debug extension. */
const char *required_extensions[16];
uint32_t required_extension_count = 0;
const char **instance_extensions = (const char**)malloc((info.enabledExtensionCount
+ ARRAY_SIZE(required_extensions)
+ ARRAY_SIZE(vulkan_optional_instance_extensions)) * sizeof(const char *));
const char **instance_layers = (const char**)malloc((info.enabledLayerCount + 1) * sizeof(const char *));
/* Both mallocs must have succeeded before the memcpy / field
* assignments below dereference the buffers. The 'end' label
@@ -1129,6 +1137,24 @@ static VkInstance vulkan_context_create_instance_wrapper(void *opaque, const VkI
case VULKAN_WSI_MVK_IOS:
required_extensions[required_extension_count++] = "VK_EXT_metal_surface";
break;
#ifdef HAVE_SDL3
case VULKAN_WSI_SDL3:
{
Uint32 sdl_ext_count = 0;
const char * const *sdl_extensions = SDL_Vulkan_GetInstanceExtensions(&sdl_ext_count);
for (i = 0; sdl_extensions && i < sdl_ext_count; i++)
{
/* VK_KHR_surface already added above. */
if (string_is_equal(sdl_extensions[i], "VK_KHR_surface"))
continue;
if (required_extension_count < ARRAY_SIZE(required_extensions))
required_extensions[required_extension_count++] = sdl_extensions[i];
else
RARCH_WARN("[Vulkan] Dropping SDL3 instance extension \"%s\": list full.\n", sdl_extensions[i]);
}
break;
}
#endif
case VULKAN_WSI_NONE:
default:
break;
@@ -1136,7 +1162,10 @@ static VkInstance vulkan_context_create_instance_wrapper(void *opaque, const VkI
#ifdef VULKAN_DEBUG
instance_layers[info.enabledLayerCount++] = "VK_LAYER_KHRONOS_validation";
required_extensions[required_extension_count++] = "VK_EXT_debug_utils";
if (required_extension_count < ARRAY_SIZE(required_extensions))
required_extensions[required_extension_count++] = "VK_EXT_debug_utils";
else
RARCH_WARN("[Vulkan] Dropping VK_EXT_debug_utils: extension list full.\n");
#endif
if (!(vulkan_find_instance_extensions(
@@ -1788,6 +1817,18 @@ bool vulkan_surface_create(gfx_ctx_vulkan_data_t *vk,
!= VK_SUCCESS)
return false;
}
#endif
break;
case VULKAN_WSI_SDL3:
#ifdef HAVE_SDL3
/* The SDL3 context driver passes its SDL_Window through the
* 'surface' parameter; SDL picks the platform surface path. */
if (!SDL_Vulkan_CreateSurface((SDL_Window*)surface, vk->context.instance, NULL, &vk->vk_surface))
{
RARCH_ERR("[Vulkan] Failed to create SDL3 surface: %s.\n",
SDL_GetError());
return false;
}
#endif
break;
case VULKAN_WSI_NONE:
@@ -2625,7 +2666,7 @@ bool vulkan_create_swapchain(gfx_ctx_vulkan_data_t *vk,
/* TODO/FIXME:
* Weird shenanigans necessary for Apple otherwise the following happens:
* The menu sometimes refuses to display, but still responds to input.
* The menu sometimes refuses to display, but still responds to input.
* This happens about 1/5 times on macOS but 100% in the quick menu on iOS
*/
#ifdef __APPLE__
+1
View File
@@ -153,6 +153,7 @@ enum vulkan_wsi_type
VULKAN_WSI_DISPLAY,
VULKAN_WSI_MVK_MACOS,
VULKAN_WSI_MVK_IOS,
VULKAN_WSI_SDL3,
};
enum vulkan_context_flags
+20 -93
View File
@@ -26,10 +26,6 @@
#include "../../config.h"
#endif
#ifdef HAVE_X11
#include "../common/x11_common.h"
#endif
#ifdef _WIN32
#include "../common/win32_common.h"
#endif
@@ -225,13 +221,8 @@ static void *sdl3_gfx_init(const video_info_t *video,
input_driver_t **input, void **input_data)
{
int i;
SDL_WindowFlags flags;
sdl3_video_t *vid = NULL;
#ifdef HAVE_X11
XInitThreads();
#endif
/* Initialize the video system. */
if (!SDL_InitSubSystem(SDL_INIT_VIDEO))
return NULL;
@@ -250,16 +241,12 @@ static void *sdl3_gfx_init(const video_info_t *video,
for (i = 0; i < SDL_GetNumRenderDrivers(); ++i)
RARCH_LOG("[SDL3] \t%s\n", SDL_GetRenderDriver(i));
if (video->fullscreen)
flags = SDL_WINDOW_FULLSCREEN;
else
{
if (!video->fullscreen)
RARCH_LOG("[SDL3] Creating window @ %ux%u.\n", video->width, video->height);
flags = SDL_WINDOW_RESIZABLE;
}
vid->window = SDL_CreateWindow("", video->width, video->height, flags);
if (!vid->window)
/* No backend flag: SDL_CreateRenderer picks the render driver. */
if (!sdl3_window_set_video_mode(&vid->window,
video->width, video->height, video->fullscreen, 0))
{
RARCH_ERR("[SDL3] Failed to init SDL window: %s.\n", SDL_GetError());
goto error;
@@ -268,25 +255,12 @@ static void *sdl3_gfx_init(const video_info_t *video,
vid->video = *video;
vid->flags |= SDL3_FLAG_SHOULD_RESIZE;
/* SDL3 only emits SDL_EVENT_TEXT_INPUT for windows that opted in;
* the SDL3 input driver forwards those events for menu text entry
* and core keyboard callbacks. Desktop platforms don't pop an
* on-screen keyboard for this. */
SDL_StartTextInput(vid->window);
if (video->fullscreen)
SDL_HideCursor();
if (!sdl3_init_renderer(vid))
goto error;
sdl3_set_handles(vid->window);
sdl3_refresh_viewport(vid);
/* The frontend selects the input driver separately. */
*input = NULL;
*input_data = NULL;
sdl3_input_driver(config_get_ptr()->arrays.input_joypad_driver, input, input_data);
return vid;
@@ -297,28 +271,15 @@ error:
static void sdl3_check_window(sdl3_video_t *vid)
{
SDL_Event event;
bool quit = false;
bool resize = false;
SDL_PumpEvents();
sdl3_pump_window_events(&quit, &resize);
/* Only consume quit + window events here; the SDL3 input driver
* drains keyboard/mouse events from the same queue. */
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_QUIT, SDL_EVENT_QUIT) > 0)
if (quit)
vid->flags |= SDL3_FLAG_QUITTING;
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_WINDOW_FIRST, SDL_EVENT_WINDOW_LAST) > 0)
{
if (event.type == SDL_EVENT_WINDOW_RESIZED || event.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED)
vid->flags |= SDL3_FLAG_SHOULD_RESIZE;
}
/* Handle the window scale event here. */
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT,
SDL_EVENT_DISPLAY_FIRST, SDL_EVENT_DISPLAY_LAST) > 0)
{
if (event.type == SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED)
vid->flags |= SDL3_FLAG_SHOULD_RESIZE;
}
if (resize)
vid->flags |= SDL3_FLAG_SHOULD_RESIZE;
}
/* Menu, statistics and widget passes compute their coordinates
@@ -507,7 +468,6 @@ static bool sdl3_gfx_frame(void *data, const void *frame, unsigned width,
unsigned height, uint64_t frame_count,
unsigned pitch, const char *msg, video_frame_info_t *video_info)
{
char title[128];
sdl3_video_t *vid = (sdl3_video_t*)data;
if (vid->flags & SDL3_FLAG_SHOULD_RESIZE)
@@ -529,10 +489,7 @@ static bool sdl3_gfx_frame(void *data, const void *frame, unsigned width,
SDL_RenderPresent(vid->renderer);
title[0] = '\0';
video_driver_get_window_title(title, sizeof(title));
if (title[0])
SDL_SetWindowTitle(vid->window, title);
sdl3_window_update_title(vid->window);
return true;
}
@@ -573,18 +530,6 @@ static bool sdl3_gfx_alive(void *data)
return !(vid->flags & SDL3_FLAG_QUITTING);
}
static bool sdl3_gfx_focus(void *data)
{
sdl3_video_t *vid = (sdl3_video_t*)data;
SDL_WindowFlags flags = (SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS);
return (SDL_GetWindowFlags(vid->window) & flags) == flags;
}
static bool sdl3_gfx_suspend_screensaver(void *data, bool enable)
{
return enable ? SDL_DisableScreenSaver() : SDL_EnableScreenSaver();
}
static bool sdl3_gfx_has_windowed(void *data)
{
/* kmsdrm backend has no windowing. Everything is an exclusive
@@ -647,18 +592,6 @@ static bool sdl3_gfx_read_viewport(void *data, uint8_t *buffer, bool is_idle)
return sdl3_capture_viewport(vid, buffer);
}
static float sdl3_get_refresh_rate(void *data)
{
sdl3_video_t *vid = (sdl3_video_t*)data;
const SDL_DisplayMode *mode;
if (!vid || !vid->window)
return 0.0f;
mode = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(vid->window));
return mode ? mode->refresh_rate : 0.0f;
}
static void sdl3_poke_set_filtering(void *data, unsigned index, bool smooth, bool ctx_scaling)
{
sdl3_video_t *vid = (sdl3_video_t*)data;
@@ -706,14 +639,6 @@ static void sdl3_poke_texture_enable(void *data, bool enable, bool full_screen)
vid->menu.active = enable;
}
static void sdl3_show_mouse(void *data, bool state)
{
if (state)
SDL_ShowCursor();
else
SDL_HideCursor();
}
static void sdl3_grab_mouse_toggle(void *data)
{
sdl3_video_t *vid = (sdl3_video_t*)data;
@@ -897,10 +822,12 @@ static void gfx_display_sdl3_scissor_begin(void *data,
if (!vid)
return;
/* gfx_display passes scissor rects in GL convention (origin
* bottom-left). SDL_SetRenderClipRect is top-left, so flip. */
/* gfx_display passes scissor rects with a top-left origin. The
* vulkan driver uses y directly and only GL flips (its scissor
* origin is bottom-left). SDL_SetRenderClipRect is top-left too,
* so we can pass the values directly to the rectangle. */
rect.x = x;
rect.y = (int)video_height - y - (int)height;
rect.y = y;
rect.w = (int)width;
rect.h = (int)height;
@@ -1638,7 +1565,7 @@ static video_poke_interface_t sdl3_video_poke_interface = {
sdl3_load_texture,
sdl3_unload_texture,
NULL, /* set_video_mode */
sdl3_get_refresh_rate,
sdl3_ctx_get_refresh_rate,
sdl3_poke_set_filtering,
NULL, /* get_video_output_size */
NULL, /* get_video_output_prev */
@@ -1672,8 +1599,8 @@ video_driver_t video_sdl3 = {
sdl3_gfx_frame,
sdl3_gfx_set_nonblock_state,
sdl3_gfx_alive,
sdl3_gfx_focus,
sdl3_gfx_suspend_screensaver,
sdl3_ctx_has_focus,
sdl3_suppress_screensaver,
sdl3_gfx_has_windowed,
NULL, /* set_shader - all call sites treat this as optional */
sdl3_gfx_free,
+12 -6
View File
@@ -2688,7 +2688,7 @@ static void vulkan_font_render_msg(
* instead of flushing the entire allocation.
* Aligns offset down and size up to nonCoherentAtomSize
* as required by the spec (§12.1). */
VkDeviceSize flush_offset = (VkDeviceSize)font->dirty_y_min
VkDeviceSize flush_offset = (VkDeviceSize)font->dirty_y_min
* staging_tex->stride
+ (VkDeviceSize)font->dirty_x_min * bpp;
VkDeviceSize flush_end = (VkDeviceSize)(font->dirty_y_max > 0
@@ -3103,6 +3103,12 @@ static struct vk_buffer_chain vulkan_buffer_chain_init(
}
static const gfx_ctx_driver_t *gfx_ctx_vk_drivers[] = {
/* SDL3 video driver is higher on the list so that when a Vulkan context
* is desired while using SDL3, it's used. The sdl3_ctx_enabled() function
* ensures it's only selected when SDL3 is actively in use. */
#if defined(HAVE_SDL3)
&gfx_ctx_sdl3_vk,
#endif
#if defined(__APPLE__)
&gfx_ctx_cocoavk,
#endif
@@ -5072,9 +5078,9 @@ static void vulkan_set_signal_semaphore(void *handle, VkSemaphore semaphore)
/* Drop every reference the frontend holds to core-owned GPU objects.
*
* Called immediately before core_reset(). Vulkan-context cores
* typically destroy and recreate their renderer inside retro_reset(),
* which invalidates the VkImage/VkImageView whose handles are cached
* in vk->hw.image, plus any per-frame semaphores still referenced
* typically destroy and recreate their renderer inside retro_reset(),
* which invalidates the VkImage/VkImageView whose handles are cached
* in vk->hw.image, plus any per-frame semaphores still referenced
* via vk->hw.semaphores[] and vk->hw.signal_semaphore.
*
* vkDeviceWaitIdle ensures the previous frame's submit, which may
@@ -7265,7 +7271,7 @@ static bool vulkan_frame(void *data, const void *frame,
((vk->flags & VK_FLAG_MENU_ENABLE) || (vk->flags & VK_FLAG_OVERLAY_ENABLE)
|| message_visible
|| statistics_show
#ifdef HAVE_GFX_WIDGETS
#ifdef HAVE_GFX_WIDGETS
|| widgets_visible
#endif
) &&
@@ -7380,7 +7386,7 @@ static bool vulkan_frame(void *data, const void *frame,
((vk->flags & VK_FLAG_MENU_ENABLE) || (vk->flags & VK_FLAG_OVERLAY_ENABLE)
|| message_visible
|| statistics_show
#ifdef HAVE_GFX_WIDGETS
#ifdef HAVE_GFX_WIDGETS
|| widgets_visible
#endif
) &&
+350
View File
@@ -0,0 +1,350 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2026 - Rob Loach
*
* 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 <http://www.gnu.org/licenses/>.
*/
/* SDL3 OpenGL context driver. While the SDL3 video driver itself is
* software rendered via SDL_Renderer, this context driver allows
* the gl/gl1/glcore video drivers to render within the SDL3 window. */
#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif
#include <string/stdstring.h>
#include "../../gfx/video_defines.h"
#include "../../gfx/video_driver.h"
#include "../../verbosity.h"
#include <SDL3/SDL.h>
#include "../common/sdl3_common.h"
typedef struct gfx_ctx_sdl3_data
{
SDL_Window *win; /* Must be first because it's shared across
* sdl3_ctx_* callbacks. */
SDL_GLContext ctx;
SDL_GLContext shared_ctx;
bool core_hw_context_enable;
bool adaptive_vsync;
} gfx_ctx_sdl3_data_t;
/* bind_api runs before init (see video_context_driver_init), so there
* is no driver instance to store these in yet. */
static enum gfx_ctx_api sdl3_gl_api = GFX_CTX_OPENGL_API;
static unsigned sdl3_gl_major = 0;
static unsigned sdl3_gl_minor = 0;
/* Cached GL contexts. Allow using the GL context across video
* rebuilds. */
static SDL_GLContext sdl3_gl_cached_ctx = NULL;
static SDL_GLContext sdl3_gl_cached_shared_ctx = NULL;
/* Core-profile context: explicitly requested, or GL 3.1+. */
static bool sdl3_gl_core_profile(gfx_ctx_sdl3_data_t *sdl)
{
if (sdl && sdl->core_hw_context_enable)
return true;
return sdl3_gl_major > 3 || (sdl3_gl_major == 3 && sdl3_gl_minor >= 1);
}
static void sdl3_ctx_destroy(void *data)
{
gfx_ctx_sdl3_data_t *sdl = (gfx_ctx_sdl3_data_t*)data;
video_driver_state_t *video_st = video_state_get_ptr();
if (!sdl)
return;
if (sdl->ctx && (video_st->flags & VIDEO_FLAG_CACHE_CONTEXT))
{
/* hw_render.cache_context reinit: keep the context alive for
* the next set_video_mode instead of destroying it. */
SDL_GL_MakeCurrent(sdl->win, NULL);
sdl3_gl_cached_ctx = sdl->ctx;
sdl3_gl_cached_shared_ctx = sdl->shared_ctx;
}
else
{
if (sdl->ctx)
SDL_GL_DestroyContext(sdl->ctx);
if (sdl->shared_ctx)
SDL_GL_DestroyContext(sdl->shared_ctx);
}
if (sdl->win)
{
SDL_StopTextInput(sdl->win);
SDL_DestroyWindow(sdl->win);
}
/* SDL3 refcounts subsystems; this balances the SDL_InitSubSystem
* in sdl3_ctx_init. While a cached context is stashed, keep our
* reference: letting the refcount hit zero would tear the context
* down with the subsystem. The next init adopts it. */
if (!sdl3_gl_cached_ctx)
SDL_QuitSubSystem(SDL_INIT_VIDEO);
free(sdl);
}
static void *sdl3_ctx_init(void *video_driver)
{
gfx_ctx_sdl3_data_t *sdl;
if (!sdl3_ctx_enabled("gl_sdl3"))
return NULL;
sdl = (gfx_ctx_sdl3_data_t*)
calloc(1, sizeof(gfx_ctx_sdl3_data_t));
if (!sdl)
return NULL;
/* When a cached context is stashed, the previous instance kept its
* video-subsystem reference to protect it - adopt that reference
* instead of initializing again. */
if (!sdl3_gl_cached_ctx && !SDL_InitSubSystem(SDL_INIT_VIDEO))
{
RARCH_WARN("[SDL3 GL] Failed to initialize SDL video subsystem: %s.\n", SDL_GetError());
free(sdl);
return NULL;
}
RARCH_LOG("[SDL3 GL] SDL %d.%d.%d gfx context driver initialized.\n",
SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION);
return sdl;
}
static enum gfx_ctx_api sdl3_ctx_get_api(void *data) { return sdl3_gl_api; }
static bool sdl3_ctx_bind_api(void *data,
enum gfx_ctx_api api, unsigned major,
unsigned minor)
{
if (api != GFX_CTX_OPENGL_API && api != GFX_CTX_OPENGL_ES_API)
return false;
sdl3_gl_api = api;
sdl3_gl_major = major;
sdl3_gl_minor = minor;
return true;
}
static void sdl3_ctx_swap_interval(void *data, int interval)
{
/* Adaptive vsync and multi-frame intervals aren't supported
* everywhere. Fallback to 1-frame interval rather than reverting
* to the previous setting. */
if (!SDL_GL_SetSwapInterval(interval) && interval != 0)
SDL_GL_SetSwapInterval(1);
}
static bool sdl3_ctx_set_video_mode(void *data,
unsigned width, unsigned height,
bool fullscreen)
{
gfx_ctx_sdl3_data_t *sdl = (gfx_ctx_sdl3_data_t*)data;
if (!sdl)
return false;
/* GL attributes must be set before the window is created. */
if (!sdl->win)
{
if (sdl3_gl_api == GFX_CTX_OPENGL_ES_API)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
else if (sdl3_gl_core_profile(sdl))
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
if (sdl3_gl_major > 0)
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, sdl3_gl_major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, sdl3_gl_minor);
}
}
if (!sdl3_window_set_video_mode(&sdl->win, width, height, fullscreen, SDL_WINDOW_OPENGL))
goto error;
/* Hold onto the context across video reinit (hw_render.cache_context). */
if (!sdl->ctx && sdl3_gl_cached_ctx)
{
sdl->ctx = sdl3_gl_cached_ctx;
sdl->shared_ctx = sdl3_gl_cached_shared_ctx;
sdl3_gl_cached_ctx = NULL;
sdl3_gl_cached_shared_ctx = NULL;
}
if (sdl->ctx)
{
/* Bind the context to the new window. */
if (SDL_GL_MakeCurrent(sdl->win, sdl->ctx))
{
video_driver_cache_context_ack_set();
RARCH_LOG("[SDL3 GL] Using cached GL context.\n");
}
else
{
/* Stale cached context: drop it and create a fresh one
* below. The ack stays unset, so the core receives a
* context_reset and rebuilds its GL state. */
RARCH_WARN("[SDL3 GL] Failed to bind cached GL context, creating anew: %s.\n",
SDL_GetError());
SDL_GL_DestroyContext(sdl->ctx);
if (sdl->shared_ctx)
SDL_GL_DestroyContext(sdl->shared_ctx);
sdl->ctx = NULL;
sdl->shared_ctx = NULL;
}
}
if (!sdl->ctx && !(sdl->ctx = SDL_GL_CreateContext(sdl->win)))
goto error;
/* Check whether or not adaptive vsync is supported. Probed on the
* cached path too: this instance is freshly alloc'd on reinit, so
* the flag would otherwise be lost with the old instance. */
sdl->adaptive_vsync = SDL_GL_SetSwapInterval(-1);
SDL_GL_SetSwapInterval(0);
return true;
error:
RARCH_WARN("[SDL3 GL] Failed to set video mode: %s.\n", SDL_GetError());
return false;
}
static void sdl3_ctx_swap_buffers(void *data)
{
gfx_ctx_sdl3_data_t *sdl = (gfx_ctx_sdl3_data_t*)data;
if (sdl && sdl->win)
SDL_GL_SwapWindow(sdl->win);
}
static gfx_ctx_proc_t sdl3_ctx_get_proc_address(const char *name)
{
return (gfx_ctx_proc_t)SDL_GL_GetProcAddress(name);
}
static uint32_t sdl3_ctx_get_flags(void *data)
{
uint32_t flags = 0;
gfx_ctx_sdl3_data_t *sdl = (gfx_ctx_sdl3_data_t*)data;
if (sdl && sdl->adaptive_vsync)
BIT32_SET(flags, GFX_CTX_FLAGS_ADAPTIVE_VSYNC);
if (sdl3_gl_core_profile(sdl))
BIT32_SET(flags, GFX_CTX_FLAGS_GL_CORE_CONTEXT);
/* Shader usage depends on the video driver:
* - glcore uses slang
* - gl uses GLSLS
* - gl1 is fixed-function, without shaders */
#if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS)
if (string_is_equal(video_driver_get_ident(), "glcore"))
BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_SLANG);
#endif
#ifdef HAVE_GLSL
if (string_is_equal(video_driver_get_ident(), "gl"))
BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_GLSL);
#endif
return flags;
}
static void sdl3_ctx_set_flags(void *data, uint32_t flags)
{
gfx_ctx_sdl3_data_t *sdl = (gfx_ctx_sdl3_data_t*)data;
if (!sdl)
return;
if (BIT32_GET(flags, GFX_CTX_FLAGS_GL_CORE_CONTEXT))
sdl->core_hw_context_enable = true;
}
static void sdl3_ctx_bind_hw_render(void *data, bool enable)
{
gfx_ctx_sdl3_data_t *sdl = (gfx_ctx_sdl3_data_t*)data;
if (!sdl || !sdl->win || !sdl->ctx)
return;
if (enable)
{
if (!sdl->shared_ctx)
{
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
SDL_GL_MakeCurrent(sdl->win, sdl->ctx);
sdl->shared_ctx = SDL_GL_CreateContext(sdl->win);
/* Reset the attribute so later SDL_GL_CreateContext calls
* (video reinit) don't silently inherit sharing. */
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0);
if (!sdl->shared_ctx)
{
RARCH_ERR("[SDL3 GL] Failed to create shared GL context: %s.\n",
SDL_GetError());
return;
}
}
SDL_GL_MakeCurrent(sdl->win, sdl->shared_ctx);
}
else
SDL_GL_MakeCurrent(sdl->win, sdl->ctx);
}
const gfx_ctx_driver_t gfx_ctx_sdl3_gl =
{
sdl3_ctx_init,
sdl3_ctx_destroy,
sdl3_ctx_get_api,
sdl3_ctx_bind_api,
sdl3_ctx_swap_interval,
sdl3_ctx_set_video_mode,
sdl3_ctx_get_video_size,
sdl3_ctx_get_refresh_rate,
NULL, /* get_video_output_size */
NULL, /* get_video_output_prev */
NULL, /* get_video_output_next */
NULL, /* get_metrics */
NULL, /* translate_aspect */
sdl3_ctx_update_title,
sdl3_ctx_check_window,
NULL, /* set_resize */
sdl3_ctx_has_focus,
sdl3_suppress_screensaver,
true, /* has_windowed */
sdl3_ctx_swap_buffers,
sdl3_ctx_input_driver,
sdl3_ctx_get_proc_address,
NULL, /* image_buffer_init */
NULL, /* image_buffer_write */
sdl3_show_mouse,
"gl_sdl3",
sdl3_ctx_get_flags,
sdl3_ctx_set_flags,
sdl3_ctx_bind_hw_render,
NULL, /* get_context_data */
NULL, /* make_current */
NULL, /* create_surface */
NULL /* destroy_surface */
};
+301
View File
@@ -0,0 +1,301 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2026 - Rob Loach
*
* 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 <http://www.gnu.org/licenses/>.
*/
/* SDL3 Vulkan context driver. While the SDL3 video driver itself is
* software rendered via SDL_Renderer, this context driver allows
* the vulkan video driver to render within the SDL3 window. */
#include <stdint.h>
#include <stdlib.h>
#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif
#include <retro_timers.h>
#include "../../verbosity.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>
#include "../common/sdl3_common.h"
#include "../common/vulkan_common.h"
typedef struct gfx_ctx_sdl3_vk_data
{
SDL_Window *win; /* Must be first because it's shared across
* sdl3_ctx_* callbacks. */
int interval;
gfx_ctx_vulkan_data_t vk;
} gfx_ctx_sdl3_vk_data_t;
static void sdl3_vk_ctx_destroy(void *data)
{
gfx_ctx_sdl3_vk_data_t *sdl = (gfx_ctx_sdl3_vk_data_t*)data;
if (!sdl)
return;
vulkan_context_destroy(&sdl->vk, sdl->win != NULL);
#if defined(HAVE_THREADS)
if (sdl->vk.context.queue_lock)
slock_free(sdl->vk.context.queue_lock);
#endif
if (sdl->win)
{
SDL_StopTextInput(sdl->win);
SDL_DestroyWindow(sdl->win);
}
SDL_Vulkan_UnloadLibrary();
/* Balances the SDL_InitSubSystem in init (SDL3 refcounts). */
SDL_QuitSubSystem(SDL_INIT_VIDEO);
free(sdl);
}
static void *sdl3_vk_ctx_init(void *video_driver)
{
gfx_ctx_sdl3_vk_data_t *sdl;
if (!sdl3_ctx_enabled("vk_sdl3"))
return NULL;
sdl = (gfx_ctx_sdl3_vk_data_t*)
calloc(1, sizeof(gfx_ctx_sdl3_vk_data_t));
if (!sdl)
return NULL;
/* SDL's X11 backend calls XInitThreads itself, so no Xlib setup
* is needed here. */
if (!SDL_InitSubSystem(SDL_INIT_VIDEO))
{
RARCH_WARN("[SDL3 Vulkan] Failed to initialize SDL video subsystem: %s.\n",
SDL_GetError());
free(sdl);
return NULL;
}
/* Load SDL's Vulkan support up front: instance creation queries
* SDL_Vulkan_GetInstanceExtensions before any window exists. */
if (!SDL_Vulkan_LoadLibrary(NULL))
{
RARCH_WARN("[SDL3 Vulkan] Failed to load Vulkan library: %s.\n",
SDL_GetError());
goto error;
}
if (!vulkan_context_init(&sdl->vk, VULKAN_WSI_SDL3))
{
SDL_Vulkan_UnloadLibrary();
goto error;
}
RARCH_LOG("[SDL3 Vulkan] SDL %d.%d.%d gfx context driver initialized.\n",
SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION);
return sdl;
error:
SDL_QuitSubSystem(SDL_INIT_VIDEO);
free(sdl);
return NULL;
}
static void sdl3_vk_ctx_swap_interval(void *data, int interval)
{
gfx_ctx_sdl3_vk_data_t *sdl = (gfx_ctx_sdl3_vk_data_t*)data;
if (sdl->interval != interval)
{
sdl->interval = interval;
if (sdl->vk.swapchain)
sdl->vk.flags |= VK_DATA_FLAG_NEED_NEW_SWAPCHAIN;
}
}
static void sdl3_vk_ctx_swap_buffers(void *data)
{
gfx_ctx_sdl3_vk_data_t *sdl = (gfx_ctx_sdl3_vk_data_t*)data;
if (sdl->vk.context.flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN)
{
sdl->vk.context.flags &= ~VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN;
if (sdl->vk.swapchain == VK_NULL_HANDLE)
{
retro_sleep(10);
}
else
vulkan_present(&sdl->vk, sdl->vk.context.current_swapchain_index);
}
vulkan_acquire_next_image(&sdl->vk);
}
static void sdl3_vk_ctx_check_window(void *data, bool *quit,
bool *resize, unsigned *width, unsigned *height)
{
gfx_ctx_sdl3_vk_data_t *sdl = (gfx_ctx_sdl3_vk_data_t*)data;
/* A pending swapchain rebuild counts as a resize; the shared
* handler pumps events and fetches the new size. */
if (sdl->vk.flags & VK_DATA_FLAG_NEED_NEW_SWAPCHAIN)
*resize = true;
sdl3_ctx_check_window(data, quit, resize, width, height);
}
static bool sdl3_vk_ctx_set_resize(void *data,
unsigned width, unsigned height)
{
gfx_ctx_sdl3_vk_data_t *sdl = (gfx_ctx_sdl3_vk_data_t*)data;
if (!sdl)
return false;
if (!vulkan_create_swapchain(&sdl->vk, width, height, sdl->interval))
{
RARCH_ERR("[SDL3 Vulkan] Failed to update swapchain.\n");
sdl->vk.swapchain = VK_NULL_HANDLE;
return false;
}
if (sdl->vk.flags & VK_DATA_FLAG_CREATED_NEW_SWAPCHAIN)
vulkan_acquire_next_image(&sdl->vk);
sdl->vk.context.flags |= VK_CTX_FLAG_INVALID_SWAPCHAIN;
sdl->vk.flags &= ~VK_DATA_FLAG_NEED_NEW_SWAPCHAIN;
return true;
}
static bool sdl3_vk_ctx_set_video_mode(void *data,
unsigned width, unsigned height,
bool fullscreen)
{
gfx_ctx_sdl3_vk_data_t *sdl = (gfx_ctx_sdl3_vk_data_t*)data;
unsigned win_width = 0;
unsigned win_height = 0;
if (!sdl)
return false;
if (!sdl3_window_set_video_mode(&sdl->win, width, height, fullscreen,
SDL_WINDOW_VULKAN))
goto error;
sdl3_window_get_video_size(sdl->win, &win_width, &win_height);
if (!vulkan_surface_create(&sdl->vk, VULKAN_WSI_SDL3,
NULL, sdl->win,
win_width, win_height, sdl->interval))
goto error;
return true;
error:
/* Avoids destroying SDL here since vulkan_init() treats a failure
* and will call vulkan_free() itself. This is the same for x_vk,
* cocoa, and android. */
RARCH_WARN("[SDL3 Vulkan] Failed to set video mode: %s.\n",
SDL_GetError());
return false;
}
static enum gfx_ctx_api sdl3_vk_ctx_get_api(void *data)
{
return GFX_CTX_VULKAN_API;
}
static bool sdl3_vk_ctx_bind_api(void *data, enum gfx_ctx_api api,
unsigned major, unsigned minor) { return (api == GFX_CTX_VULKAN_API); }
/* Vulkan HW-render cores share the frontend's VkDevice through
* context negotiation - there is no context to switch. */
static void sdl3_vk_ctx_bind_hw_render(void *data, bool enable) { }
static void *sdl3_vk_ctx_get_context_data(void *data)
{
gfx_ctx_sdl3_vk_data_t *sdl = (gfx_ctx_sdl3_vk_data_t*)data;
return &sdl->vk.context;
}
static uint32_t sdl3_vk_ctx_get_flags(void *data)
{
gfx_ctx_sdl3_vk_data_t *sdl = (gfx_ctx_sdl3_vk_data_t*)data;
uint32_t flags = 0;
uint8_t present_mode_count = 16;
uint8_t i = 0;
/* Check for FIFO_RELAXED_KHR capability. */
if (sdl)
{
for (i = 0; i < present_mode_count; i++)
{
if (sdl->vk.context.present_modes[i] == VK_PRESENT_MODE_FIFO_RELAXED_KHR)
{
BIT32_SET(flags, GFX_CTX_FLAGS_ADAPTIVE_VSYNC);
break;
}
}
}
#if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS)
BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_SLANG);
#endif
return flags;
}
static void sdl3_vk_ctx_set_flags(void *data, uint32_t flags) { }
const gfx_ctx_driver_t gfx_ctx_sdl3_vk = {
sdl3_vk_ctx_init,
sdl3_vk_ctx_destroy,
sdl3_vk_ctx_get_api,
sdl3_vk_ctx_bind_api,
sdl3_vk_ctx_swap_interval,
sdl3_vk_ctx_set_video_mode,
sdl3_ctx_get_video_size,
sdl3_ctx_get_refresh_rate,
NULL, /* get_video_output_size */
NULL, /* get_video_output_prev */
NULL, /* get_video_output_next */
NULL, /* get_metrics */
NULL, /* translate_aspect */
sdl3_ctx_update_title,
sdl3_vk_ctx_check_window,
sdl3_vk_ctx_set_resize,
sdl3_ctx_has_focus,
sdl3_suppress_screensaver,
true, /* has_windowed */
sdl3_vk_ctx_swap_buffers,
sdl3_ctx_input_driver,
NULL, /* get_proc_address */
NULL, /* image_buffer_init */
NULL, /* image_buffer_write */
sdl3_show_mouse,
"vk_sdl3",
sdl3_vk_ctx_get_flags,
sdl3_vk_ctx_set_flags,
sdl3_vk_ctx_bind_hw_render,
sdl3_vk_ctx_get_context_data,
NULL, /* make_current */
NULL, /* create_surface */
NULL /* destroy_surface */
};
+8
View File
@@ -25,6 +25,7 @@
#endif
#include "../msg_hash.h"
#include "../verbosity.h"
#include "font_driver.h"
#include "video_thread_wrapper.h"
@@ -1021,7 +1022,14 @@ void font_driver_init_osd(
video_driver_state_t *video_st = video_state_get_ptr();
if (video_st->osd_font && video_st->osd_font_owner != video_data)
{
font_data_t *stale = (font_data_t*)video_st->osd_font;
RARCH_WARN("[Font] Discarding stale OSD font from a previous video driver instance.\n");
stale->renderer = NULL;
stale->renderer_data = NULL;
font_driver_free_osd();
}
/* threading_hint is false: both callers - video_driver_init_internal()
* and the threaded wrapper's CMD_INIT - already run on the thread that
+12 -9
View File
@@ -163,6 +163,12 @@ static const gfx_ctx_driver_t *gfx_ctx_gl_drivers[] = {
#if defined(HAVE_OPENDINGUX_FBDEV)
&gfx_ctx_opendingux_fbdev,
#endif
/* SDL3 video driver is higher on the list so that when an OpenGL context
* is desired while using SDL3, it's used. The sdl3_ctx_enabled() function
* ensures it's only selected when SDL3 is actively in use. */
#if defined(HAVE_SDL3) && (defined(HAVE_OPENGL) || defined(HAVE_OPENGL1) || defined(HAVE_OPENGL_CORE) || defined(HAVE_OPENGLES))
&gfx_ctx_sdl3_gl,
#endif
#if defined(_WIN32) && (defined(HAVE_OPENGL) || defined(HAVE_OPENGL1) || defined(HAVE_OPENGL_CORE)) && !defined(HAVE_ANGLE)
&gfx_ctx_wgl,
#endif
@@ -1981,14 +1987,10 @@ void video_driver_free_internal(void)
* the rest still free it inside their own free(). Keyed on
* ownership so that a teardown arriving after the next driver is
* already up cannot take the live font with it. Under threaded
* video the wrapper does this from CMD_FREE, on the video thread. */
#ifdef HAVE_THREADS
if (!is_threaded)
#endif
{
if (vid && vid->font_backend)
font_driver_free_osd_for(video_st->data);
}
* video the wrapper does this from CMD_FREE, so we don't need to
* check HAVE_THREADS here. */
if (vid && vid->font_backend)
font_driver_free_osd_for(video_st->data);
if (video_st->data && vid && vid->free)
vid->free(video_st->data);
@@ -4328,7 +4330,8 @@ bool video_driver_init_internal(bool *video_is_threaded, bool verbosity_enabled)
#endif
{
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || \
(defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH))
(defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || \
defined(HAVE_SDL3)
bool window_custom_size_enable = settings->bools.video_window_save_positions;
#else
bool window_custom_size_enable = settings->bools.video_window_custom_size_enable;
+2
View File
@@ -1570,6 +1570,8 @@ extern video_driver_t video_null;
extern const gfx_ctx_driver_t gfx_ctx_osmesa;
extern const gfx_ctx_driver_t gfx_ctx_sdl_gl;
extern const gfx_ctx_driver_t gfx_ctx_sdl3_gl;
extern const gfx_ctx_driver_t gfx_ctx_sdl3_vk;
extern const gfx_ctx_driver_t gfx_ctx_x_egl;
extern const gfx_ctx_driver_t gfx_ctx_uwp;
extern const gfx_ctx_driver_t gfx_ctx_vk_wayland;
+6
View File
@@ -977,6 +977,12 @@ AUDIO
#include "../input/drivers/sdl3_input.c"
#include "../gfx/drivers/sdl3_gfx.c"
#include "../gfx/common/sdl3_common.c"
#if defined(HAVE_OPENGL) || defined(HAVE_OPENGL1) || defined(HAVE_OPENGL_CORE) || defined(HAVE_OPENGLES)
#include "../gfx/drivers_context/sdl3_gl_ctx.c"
#endif
#if defined(HAVE_VULKAN)
#include "../gfx/drivers_context/sdl3_vk_ctx.c"
#endif
#elif defined(HAVE_SDL2)
#include "../audio/drivers/sdl_audio.c"
#include "../input/drivers/sdl_input.c"
+12 -5
View File
@@ -471,13 +471,20 @@ static bool sdl3_set_sensor_state(void *data, unsigned port,
/* Gets the SDL_Window, if it exists. */
static SDL_Window *sdl3_input_window(void)
{
sdl3_video_t *video_ptr;
gfx_ctx_ident_t ident_info;
if (string_is_not_equal(video_driver_get_ident(), "sdl3"))
return NULL;
if (string_is_equal(video_driver_get_ident(), "sdl3"))
{
sdl3_video_t *video_ptr = (sdl3_video_t*)video_driver_get_ptr();
return video_ptr != NULL ? video_ptr->window : NULL;
}
if ((video_ptr = (sdl3_video_t*)video_driver_get_ptr()))
return video_ptr->window;
/* gl/gl1/glcore/vulkan running on the SDL3 context drivers. They
* register their SDL_Window as the display userdata via sdl3_set_handles. */
ident_info.ident = NULL;
video_context_driver_get_ident(&ident_info);
if (string_is_equal(ident_info.ident, "gl_sdl3") || string_is_equal(ident_info.ident, "vk_sdl3"))
return (SDL_Window*)video_driver_display_userdata_get();
return NULL;
}
+4 -2
View File
@@ -10254,14 +10254,16 @@ unsigned menu_displaylist_build_list(
case DISPLAYLIST_VIDEO_WINDOWED_MODE_SETTINGS_LIST:
{
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || \
(defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH))
(defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || \
defined(HAVE_SDL3)
bool window_custom_size_enable = settings->bools.video_window_save_positions;
#else
bool window_custom_size_enable = settings->bools.video_window_custom_size_enable;
#endif
static menu_displaylist_build_info_selective_t build_list[] = {
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || \
(defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH))
(defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || \
defined(HAVE_SDL3)
{MENU_ENUM_LABEL_VIDEO_WINDOW_SAVE_POSITION, PARSE_ONLY_BOOL, true },
#else
{MENU_ENUM_LABEL_VIDEO_WINDOW_CUSTOM_SIZE_ENABLE, PARSE_ONLY_BOOL, true },
+6 -5
View File
@@ -11613,14 +11613,14 @@ static const setting_desc_t vid_desc_15[] = {
};
#endif
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH))
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || defined(HAVE_SDL3)
static const setting_desc_t vid_desc_16[] = {
/* GENERATED: rows come from settings_def_video_window_save_position.h in order. */
#include "../settings/settings_def_video_window_save_position.h"
};
#endif
#if !((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)))
#if !((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || defined(HAVE_SDL3))
static const setting_desc_t vid_desc_17[] = {
/* GENERATED: rows come from settings_def_video_window_custom_size.h in order. */
#include "../settings/settings_def_video_window_custom_size.h"
@@ -14461,7 +14461,8 @@ static void settings_build_video(
ADD_DESC(vid_desc_15);
#endif
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || \
(defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH))
(defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || \
defined(HAVE_SDL3)
ADD_DESC(vid_desc_16);
#else
ADD_DESC(vid_desc_17);
@@ -17748,10 +17749,10 @@ static const settings_desc_table_t settings_desc_registry[] = {
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
{ vid_desc_15, (uint16_t)ARRAY_SIZE(vid_desc_15) },
#endif
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH))
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || defined(HAVE_SDL3)
{ vid_desc_16, (uint16_t)ARRAY_SIZE(vid_desc_16) },
#endif
#if !((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)))
#if !((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || defined(HAVE_SDL3))
{ vid_desc_17, (uint16_t)ARRAY_SIZE(vid_desc_17) },
#endif
{ video2_desc_0, (uint16_t)ARRAY_SIZE(video2_desc_0) },
@@ -4,9 +4,9 @@
* matches SDESC_<kind>_ROW; row order is menu display order;
* h2json.py parses these rows for the Crowdin source upload. */
/* Descriptor and configuration rows are #if !((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH))); the string
/* Descriptor and configuration rows are #if !((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || defined(HAVE_SDL3)); the string
* tables always carry this row via the strings pass. */
#if (!((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)))) || defined(SETTINGS_DEF_STRINGS_PASS)
#if (!((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || defined(HAVE_SDL3))) || defined(SETTINGS_DEF_STRINGS_PASS)
S_BOOL_EX(video_window_custom_size_enable, VIDEO_WINDOW_CUSTOM_SIZE_ENABLE,
"video_window_custom_size_enable",
DEFAULT_WINDOW_CUSTOM_SIZE_ENABLE, SD_FLAG_NONE, 0, CMD_EVENT_REINIT, setting_bool_action_left_with_refresh, NULL, NULL, NULL, setting_bool_action_left_with_refresh, setting_bool_action_right_with_refresh, 0,
@@ -4,9 +4,9 @@
* matches SDESC_<kind>_ROW; row order is menu display order;
* h2json.py parses these rows for the Crowdin source upload. */
/* Descriptor and configuration rows are #if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)); the string
/* Descriptor and configuration rows are #if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || defined(HAVE_SDL3); the string
* tables always carry this row via the strings pass. */
#if ((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH))) || defined(SETTINGS_DEF_STRINGS_PASS)
#if ((defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || (defined(HAVE_COCOA_METAL) && !defined(HAVE_COCOATOUCH)) || defined(HAVE_SDL3)) || defined(SETTINGS_DEF_STRINGS_PASS)
/* config key "video_window_save_positions" differs from the label string; the
* configuration.c row stays literal for this setting. */
#ifndef SETTINGS_DEF_CONFIG_PASS