video: add Exclusive Fullscreen Negotiation, and honour Forced on Vulkan

Windowed Fullscreen Mode off was supposed to mean exclusive
fullscreen. On the Vulkan driver it reached the swapchain as
VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT, which is a permission the driver
may decline - and NVIDIA declines it. PresentMon on a 5090 reports
"Hardware Composed: Independent Flip" with the setting in either state:
the swapchain sits on DWM's multi-plane-overlay path regardless, and
the toggle is inert. vkAcquireFullScreenExclusiveModeEXT was resolved
in the vendored header and never called anywhere.

Add a setting beside it. Relaxed is the current behaviour, ALLOWED and
nothing more, and stays the default. Forced sets
VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT on the surface and
calls vkAcquireFullScreenExclusiveModeEXT once the swapchain exists.
A failed acquire is a warning and the swapchain carries on where
Relaxed would have left it. VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
- alt-tab, a mode switch - takes the same rebuild path as
VK_ERROR_OUT_OF_DATE_KHR, and the mode is released before the
swapchain it is bound to is destroyed.

What Forced buys is not asserted here because it has not been
measured: possibly lower present-to-scanout latency than the
compositor path, genuinely immediate presentation that Scanline Sync
could position against, and the application owning the display's HDR
state rather than negotiating it through DWM. This is the switch that
makes those measurable.

The Windows-only pieces are under VK_USE_PLATFORM_WIN32_KHR. The
setting exists on every platform and does nothing where the extension
is absent. tools/settings_table.reference regenerated; the three
Settings-Checks jobs pass locally.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-02 05:42:04 +02:00
1 parent c993b7ac34
commit 70e5889d9a
11 files changed
+119 -1

No files matched your search

+4
View File
@@ -264,6 +264,10 @@
#define DEFAULT_WINDOWED_FULLSCREEN true
#endif
/* Not platform-specific: how hard to push for exclusive fullscreen
* where the platform lets the application decide. */
#define DEFAULT_VIDEO_FSE_NEGOTIATION VIDEO_FSE_RELAXED
/* Enable automatic switching of the screen refresh rate when using the specified screen mode(s),
* based on running core/content */
#define DEFAULT_AUTOSWITCH_REFRESH_RATE AUTOSWITCH_REFRESH_RATE_EXCLUSIVE_FULLSCREEN
+1
View File
@@ -239,6 +239,7 @@ typedef struct settings
unsigned video_viwidth;
unsigned video_aspect_ratio_idx;
unsigned video_rotation;
unsigned video_fse_negotiation;
unsigned screen_orientation;
unsigned video_msg_bgcolor_red;
unsigned video_msg_bgcolor_green;
+58 -1
View File
@@ -1029,6 +1029,22 @@ static bool vulkan_context_init_device(gfx_ctx_vulkan_data_t *vk)
return false;
}
#ifdef VK_USE_PLATFORM_WIN32_KHR
/* Resolve the explicit acquire/release now the device exists. Only
* used under VIDEO_FSE_FORCED; NULL otherwise and never called. */
vk->fse_acquire = NULL;
vk->fse_release = NULL;
if (vk->fse_supported)
{
vk->fse_acquire = (PFN_vkAcquireFullScreenExclusiveModeEXT)
vkGetDeviceProcAddr(vk->context.device,
"vkAcquireFullScreenExclusiveModeEXT");
vk->fse_release = (PFN_vkReleaseFullScreenExclusiveModeEXT)
vkGetDeviceProcAddr(vk->context.device,
"vkReleaseFullScreenExclusiveModeEXT");
}
#endif
#ifdef VULKAN_HDR_SWAPCHAIN
/* Now that the device exists, resolve vkSetHdrMetadataEXT if the
* extension was enabled above. Stays NULL (call skipped) otherwise. */
@@ -1470,6 +1486,12 @@ end:
static void vulkan_destroy_swapchain(gfx_ctx_vulkan_data_t *vk)
{
#ifdef VK_USE_PLATFORM_WIN32_KHR
/* Exclusive mode is bound to the swapchain; release it first. */
if (vk->fse_acquired && vk->fse_release && vk->swapchain != VK_NULL_HANDLE)
vk->fse_release(vk->context.device, vk->swapchain);
vk->fse_acquired = false;
#endif
unsigned i;
vulkan_emulated_mailbox_deinit(&vk->mailbox);
@@ -2038,6 +2060,11 @@ retry:
/* Do nothing. */
break;
case VK_ERROR_OUT_OF_DATE_KHR:
#ifdef VK_USE_PLATFORM_WIN32_KHR
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
/* Alt-tab or a mode switch under Forced negotiation. Same
* recovery as out-of-date: rebuild and re-acquire. */
#endif
/* Throw away the old swapchain and try again. */
vulkan_destroy_swapchain(vk);
/* Swapchain out of date, trying to create new one ... */
@@ -2107,13 +2134,24 @@ bool vulkan_create_swapchain(gfx_ctx_vulkan_data_t *vk,
bool adaptive_vsync = settings->bools.video_adaptive_vsync;
#ifdef VK_USE_PLATFORM_WIN32_KHR
bool video_windowed_fullscreen = settings->bools.video_windowed_fullscreen;
/* Relaxed: ALLOWED is a hint and the driver may decline - and on
* NVIDIA it does, leaving the swapchain on DWM's independent-flip
* path with the setting silently inert (PresentMon reports
* "Hardware Composed: Independent Flip" in both windowed-fullscreen
* states). Forced: APPLICATION_CONTROLLED, followed by an explicit
* vkAcquireFullScreenExclusiveModeEXT once the swapchain exists. */
bool fse_forced =
!video_windowed_fullscreen
&& settings->uints.video_fse_negotiation == VIDEO_FSE_FORCED;
HMONITOR hmonitor;
VkSurfaceFullScreenExclusiveInfoEXT fse_info = {
VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT,
NULL,
video_windowed_fullscreen
? VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT
: VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT
: (fse_forced
? VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT
: VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT)
};
VkSurfaceFullScreenExclusiveWin32InfoEXT fse_win32_info = {
VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT,
@@ -2698,6 +2736,25 @@ bool vulkan_create_swapchain(gfx_ctx_vulkan_data_t *vk,
}
}
#ifdef VK_USE_PLATFORM_WIN32_KHR
vk->fse_acquired = false;
if (fse_forced && vk->fse_supported && vk->fse_acquire)
{
VkResult res = vk->fse_acquire(vk->context.device, vk->swapchain);
if (res == VK_SUCCESS)
{
vk->fse_acquired = true;
RARCH_LOG("[Vulkan] Exclusive fullscreen acquired.\n");
}
else
/* Not fatal: the swapchain still works on the compositor
* path, which is where Relaxed would have left it anyway. */
RARCH_WARN("[Vulkan] Exclusive fullscreen requested but "
"vkAcquireFullScreenExclusiveModeEXT returned %d; "
"continuing without it.\n", (int)res);
}
#endif
/* See TODO/FIXME note above - part of the same rubber bandaid hack 'fix' */
#ifndef __APPLE__
if (old_swapchain != VK_NULL_HANDLE)
+8
View File
@@ -267,6 +267,14 @@ typedef struct gfx_ctx_vulkan_data
uint8_t flags;
enum vulkan_wsi_type wsi_type;
bool fse_supported;
/* Set once VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT has
* been acquired on the current swapchain, so it is released before
* that swapchain is destroyed. */
bool fse_acquired;
#ifdef VK_USE_PLATFORM_WIN32_KHR
PFN_vkAcquireFullScreenExclusiveModeEXT fse_acquire;
PFN_vkReleaseFullScreenExclusiveModeEXT fse_release;
#endif
#ifdef VULKAN_HDR_SWAPCHAIN
/* Loaded from VK_EXT_hdr_metadata when that optional device extension is
* present; NULL otherwise. Used to signal SMPTE-2086 mastering-display
+9
View File
@@ -102,6 +102,15 @@ enum video_rotation_type
VIDEO_ROTATION_270_DEG
};
/* How hard to push for exclusive fullscreen where the platform lets the
* application decide (VK_EXT_full_screen_exclusive on Windows Vulkan). */
enum video_fse_negotiation
{
VIDEO_FSE_RELAXED = 0, /* hint only; the driver may decline */
VIDEO_FSE_FORCED, /* take it explicitly and hold it */
VIDEO_FSE_LAST
};
enum autoswitch_refresh_rate
{
AUTOSWITCH_REFRESH_RATE_EXCLUSIVE_FULLSCREEN = 0,
+8
View File
@@ -25350,6 +25350,14 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_SELECT_FILE,
"Select File"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_FSE_RELAXED,
"Relaxed"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_FSE_FORCED,
"Forced"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_VIDEO_ROTATION_NORMAL,
"Normal"
+1
View File
@@ -10625,6 +10625,7 @@ unsigned menu_displaylist_build_list(
static menu_displaylist_build_info_selective_t build_list[] = {
{MENU_ENUM_LABEL_VIDEO_FULLSCREEN, PARSE_ONLY_BOOL, true },
{MENU_ENUM_LABEL_VIDEO_WINDOWED_FULLSCREEN, PARSE_ONLY_BOOL, true },
{MENU_ENUM_LABEL_VIDEO_FSE_NEGOTIATION, PARSE_ONLY_UINT, true },
{MENU_ENUM_LABEL_VIDEO_FULLSCREEN_X, PARSE_ONLY_UINT, true },
{MENU_ENUM_LABEL_VIDEO_FULLSCREEN_Y, PARSE_ONLY_UINT, true },
#ifdef __WINRT__
+20
View File
@@ -6981,6 +6981,26 @@ static size_t setting_get_string_representation_video_frame_delay(
return _len;
}
static size_t setting_get_string_representation_uint_video_fse_negotiation(
rarch_setting_t *setting, char *s, size_t len)
{
if (setting)
{
switch (*setting->value.target.unsigned_integer)
{
case VIDEO_FSE_RELAXED:
return strlcpy(s,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_FSE_RELAXED),
len);
case VIDEO_FSE_FORCED:
return strlcpy(s,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_FSE_FORCED),
len);
}
}
return 0;
}
static size_t setting_get_string_representation_uint_video_rotation(
rarch_setting_t *setting, char *s, size_t len)
{
+2
View File
@@ -8410,6 +8410,8 @@ enum msg_hash_enums
MENU_LABEL(VIDEO_GAMMA),
MENU_LABEL(VIDEO_ALLOW_ROTATE),
MENU_ENUM_LABEL_VALUE_VIDEO_FSE_RELAXED,
MENU_ENUM_LABEL_VALUE_VIDEO_FSE_FORCED,
MENU_ENUM_LABEL_VALUE_VIDEO_ROTATION_NORMAL,
MENU_ENUM_LABEL_VALUE_VIDEO_ROTATION_90_DEG,
MENU_ENUM_LABEL_VALUE_VIDEO_ROTATION_180_DEG,
+5
View File
@@ -14,6 +14,11 @@ S_BOOL(video_windowed_fullscreen, VIDEO_WINDOWED_FULLSCREEN,
DEFAULT_WINDOWED_FULLSCREEN, SD_FLAG_LAKKA_ADVANCED, 0, CMD_EVENT_NONE,
"Windowed Fullscreen Mode",
"If fullscreen, prefer using a fullscreen window to prevent display mode switching.")
S_UINT(video_fse_negotiation, VIDEO_FSE_NEGOTIATION,
"video_fse_negotiation",
DEFAULT_VIDEO_FSE_NEGOTIATION, SD_FLAG_LAKKA_ADVANCED, SDESC_RANGE_MINMAX, CMD_EVENT_REINIT, 0, VIDEO_FSE_LAST - 1, 1, 0, setting_action_ok_uint, setting_get_string_representation_uint_video_fse_negotiation,
"Exclusive Fullscreen Negotiation",
"How hard to ask the driver for exclusive fullscreen when Windowed Fullscreen Mode is off. 'Relaxed' only permits it and the driver may decline; on the Vulkan driver that is a hint the driver has been seen to ignore, leaving the swapchain on the compositor's independent-flip path. 'Forced' takes exclusive mode explicitly and holds it. Requires VK_EXT_full_screen_exclusive.")
#if !defined(__WINRT__) || !defined(SETTINGS_DEF_CONFIG_PASS)
S_UINT(video_fullscreen_x, VIDEO_FULLSCREEN_X,
"video_fullscreen_x",
+3
View File
@@ -429,6 +429,7 @@ uint video_windowed_position_x 1 0
uint video_windowed_position_y 1 0
uint video_windowed_position_width 1 1280
uint video_windowed_position_height 1 720
uint video_fse_negotiation 1 VIDEO_FSE_RELAXED
uint video_fullscreen_x 1 0
uint video_fullscreen_y 1 0
uint video_swap_interval 1 1
@@ -1075,6 +1076,7 @@ uint video_windowed_position_x 1 0
uint video_windowed_position_y 1 0
uint video_windowed_position_width 1 1280
uint video_windowed_position_height 1 720
uint video_fse_negotiation 1 VIDEO_FSE_RELAXED
uint video_fullscreen_x 1 0
uint video_fullscreen_y 1 0
uint video_swap_interval 1 1
@@ -1724,6 +1726,7 @@ uint video_windowed_position_x 1 0
uint video_windowed_position_y 1 0
uint video_windowed_position_width 1 1280
uint video_windowed_position_height 1 720
uint video_fse_negotiation 1 VIDEO_FSE_RELAXED
uint video_fullscreen_x 1 0
uint video_fullscreen_y 1 0
uint video_swap_interval 1 1