(Vulkan) Android presents on the graphics queue

Since 5d950a7288 the frontend presents on the graphics family's second
queue wherever the family has one. Behind Android's WSI the loader
turns a present's wait semaphore into a sync fd through the driver's
QueueSignalReleaseImageANDROID, and Mali (G615 MC6, r44p1, which
reports two queues in family 0) fails that call when the semaphore was
signalled on another queue: every present fails, the failed present
destroys the swapchain, the next acquire rebuilds it, and the menu
freezes in that loop (#19601). A CI build from before 3df4bd91b7 - the
swapchain-teardown hazard fixed in 4823918abe - already froze, so that
fix alone does not cover it.

The choice is made in one place now, vulkan_select_present_queue_index():
queue 1 where the family has a second queue, queue 0 behind Android's
WSI. Android's WSI is VULKAN_WSI_ANDROID, and any context driver in an
ANDROID build, the SDL3 one included. A core that provides its own
presentation queue in the graphics family gets the graphics queue
there as well; one in another family is still refused as before. The
desktop drivers keep the second queue and its lock-free presents.

The vulkan_validation sample checks the policy ahead of its display
check, so it runs on every lane: second queue elsewhere, the graphics
queue behind Android's WSI at one, two and four queues. The old
selection behind the same helper fails it on the Android cases.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-22 12:20:19 +02:00
1 parent 4823918abe
commit c265d78ee9
3 files changed
+93 -5

No files matched your search

+36 -5
View File
@@ -804,6 +804,25 @@ static VkDevice vulkan_context_create_device_wrapper(
return device;
}
uint32_t vulkan_select_present_queue_index(bool android_wsi,
uint32_t family_queue_count)
{
if (android_wsi)
return 0;
return (family_queue_count >= 2) ? 1 : 0;
}
/* Android's WSI, whichever context driver brought the surface up - the
* SDL3 one reaches the same loader there. */
static bool vulkan_wsi_is_android(const gfx_ctx_vulkan_data_t *vk)
{
#ifdef ANDROID
return true;
#else
return vk->wsi_type == VULKAN_WSI_ANDROID;
#endif
}
static bool vulkan_context_init_device(gfx_ctx_vulkan_data_t *vk)
{
uint32_t queue_count;
@@ -936,8 +955,18 @@ static bool vulkan_context_init_device(gfx_ctx_vulkan_data_t *vk)
context.queue_family_index);
return false;
}
vk->context.present_queue = context.presentation_queue;
RARCH_LOG("[Vulkan] Core provided a separate presentation queue.\n");
/* Same family, so the graphics queue can present to the
* surface as well; behind Android's WSI it does, rather
* than the core's second queue (see the declaration of
* vulkan_select_present_queue_index()). */
if (vulkan_select_present_queue_index(
vulkan_wsi_is_android(vk), 2))
{
vk->context.present_queue = context.presentation_queue;
RARCH_LOG("[Vulkan] Core provided a separate presentation queue.\n");
}
else
RARCH_LOG("[Vulkan] Core provided a separate presentation queue; presenting on the graphics queue under Android's WSI.\n");
}
}
else
@@ -1024,9 +1053,11 @@ static bool vulkan_context_init_device(gfx_ctx_vulkan_data_t *vk)
RARCH_LOG("[Vulkan] Queue family %u supports %u sub-queues.\n",
i, queue_properties[i].queueCount);
/* A second queue of the family, when there is one, takes
* the presents off the graphics queue and its lock. */
if (queue_properties[i].queueCount >= 2)
present_queue_index = 1;
* the presents off the graphics queue and its lock - except
* behind Android's WSI; see the declaration. */
present_queue_index = vulkan_select_present_queue_index(
vulkan_wsi_is_android(vk),
queue_properties[i].queueCount);
found_queue = true;
break;
}
+9
View File
@@ -418,6 +418,15 @@ void vulkan_debug_mark_buffer(VkDevice device, VkBuffer buffer);
bool vulkan_context_init(gfx_ctx_vulkan_data_t *vk,
enum vulkan_wsi_type type);
/* Which queue of the graphics family the frontend presents on: 1 when
* the family has a second queue, so presents stay off the graphics
* queue and its lock, and 0 otherwise. Always 0 behind Android's WSI:
* there the loader turns a present's semaphore wait into a sync fd
* through the driver's QueueSignalReleaseImageANDROID, and Mali fails
* that call for a semaphore signalled on another queue (#19601). */
uint32_t vulkan_select_present_queue_index(bool android_wsi,
uint32_t family_queue_count);
#ifdef __APPLE__
/* Returns the version string of the MoltenVK implementation in use,
* captured at Vulkan context creation. Returns an empty string if no
@@ -659,11 +659,59 @@ static int test_memory_type_selection(void)
return 0;
}
/* The present-queue policy (#19601): the frontend presents on the
* family's second queue where there is one, except behind Android's
* WSI, where Mali fails QueueSignalReleaseImageANDROID for a present
* whose semaphore another queue signalled - every present failed, the
* swapchain was rebuilt each frame and the menu froze. The Android WSI
* cannot run here, so the policy is checked where it is decided. Needs
* no device or display. */
static int test_present_queue_policy(void)
{
static const struct
{
bool android;
uint32_t count;
uint32_t want;
} cases[] = {
{ false, 1, 0 },
{ false, 2, 1 },
{ false, 4, 1 },
{ true, 1, 0 },
{ true, 2, 0 },
{ true, 4, 0 },
};
unsigned i;
int fail = 0;
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
{
uint32_t got = vulkan_select_present_queue_index(
cases[i].android, cases[i].count);
if (got != cases[i].want)
{
fprintf(stderr, "FAIL: %s WSI, %u queue(s) in the family:"
" presents on queue %u, want %u\n",
cases[i].android ? "Android" : "non-Android",
cases[i].count, got, cases[i].want);
fail = 1;
}
}
if (!fail)
printf("[pass] presents stay on the graphics queue behind"
" Android's WSI, and take a second queue elsewhere\n");
return fail;
}
int main(void)
{
int skipped = 0;
int ret = 0;
/* Pure policy, ahead of the display check so it runs everywhere. */
if (test_present_queue_policy())
return 1;
if (!(s_dpy = XOpenDisplay(NULL)))
{
fputs("SKIP: no X display (try Xvfb; see the header"