From bb58d0cc7b1501ac7fa2adc6e35a720cf47ee5b5 Mon Sep 17 00:00:00 2001 From: XxJSONDeruloxX Date: Wed, 14 Oct 2026 21:52:55 -0400 Subject: [PATCH] fix: retain Vulkan semaphore dependencies --- docs/development/quest-openxr.md | 15 +- tools/tests/test_vr_semaphore_layer.py | 26 +++ tools/vr/src/vk_layer_droiddeck_vr.c | 237 +++++-------------------- tools/vr/tests/test_semaphore_layer.c | 130 ++++++++++++++ 4 files changed, 218 insertions(+), 190 deletions(-) create mode 100644 tools/tests/test_vr_semaphore_layer.py create mode 100644 tools/vr/tests/test_semaphore_layer.c diff --git a/docs/development/quest-openxr.md b/docs/development/quest-openxr.md index 12fb4c1a4..0c4bde818 100644 --- a/docs/development/quest-openxr.md +++ b/docs/development/quest-openxr.md @@ -292,8 +292,7 @@ A minimal native Vulkan probe on the real Quest reports export/import support for SYNC_FD fences/semaphores, but not OPAQUE_FD handles. This distinguishes the unsupported implicit DMA-buf fence export from available explicit Vulkan GPU completion primitives. Implementing the explicit producer synchronization is -still necessary; the existing shared-semaphore wait-dropping workaround must -not be treated as correct synchronization. The desktop's dashboard helper also +still necessary. The desktop's dashboard helper also lacks X display authorization; no authentication or permission check was bypassed to work around that error. @@ -310,6 +309,18 @@ bundled into the user-facing runtime. A valid already-signaled SYNC_FD value of ## Remaining stages +The Vulkan layer no longer drops imported binary waits or unsignaled timeline +waits after a three-millisecond grace period. Queue submissions and host waits +retain their original arguments, deadlines and downstream results. The +software fallback supports permanent OPAQUE_FD timeline payloads only; binary, +temporary and malformed fallback imports fail explicitly without consuming +the caller's descriptor. Native handles still pass through unchanged. Host +regressions exercise both submit APIs, pending values, device-loss propagation, +65-semaphore host waits, import failures, re-export and descriptor cleanup. +These are API-contract tests, not Quest GPU or SteamVR game validation. Native +binary SYNC_FD support remains available, but adapting SteamVR's unsupported +OPAQUE_FD binary usage still requires a correctly synchronized implementation. + Steam's ARM64 watchdog also aborts with `futex robust_list not initialized by pthreads`. A captured preload trace showed successful own-thread queries leaving `errno=EFAULT`. The preload now prefers the real kernel query, falls diff --git a/tools/tests/test_vr_semaphore_layer.py b/tools/tests/test_vr_semaphore_layer.py new file mode 100644 index 000000000..2768160af --- /dev/null +++ b/tools/tests/test_vr_semaphore_layer.py @@ -0,0 +1,26 @@ +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +@unittest.skipUnless(shutil.which("gcc"), "needs gcc on a Linux host") +class VrSemaphoreLayerTest(unittest.TestCase): + def test_waits_are_never_fabricated(self): + repository = Path(__file__).resolve().parents[2] + candidates = [os.environ.get("VK_INCLUDE", ""), "/usr/include", repository / "build/vr/include"] + candidates += [path for root in (os.environ.get("ANDROID_HOME"), os.environ.get("ANDROID_SDK_ROOT")) if root + for path in Path(root).glob("ndk/*/toolchains/llvm/prebuilt/*/sysroot/usr/include")] + headers = next((Path(path) for path in candidates if path and (Path(path) / "vulkan/vk_layer.h").is_file()), None) + if headers is None: + self.skipTest("needs Vulkan headers") + with tempfile.TemporaryDirectory() as directory: + executable = Path(directory) / "semaphore-layer-test" + subprocess.run(["gcc", "-std=c11", "-O2", "-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter", + "-pthread", "-I" + str(headers), str(repository / "tools/vr/tests/test_semaphore_layer.c"), + "-ldl", "-o", str(executable)], check=True) + result = subprocess.run([str(executable)], capture_output=True, text=True, timeout=20) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn("fd ownership passed", result.stdout) diff --git a/tools/vr/src/vk_layer_droiddeck_vr.c b/tools/vr/src/vk_layer_droiddeck_vr.c index d1bb2bd21..f824c52ba 100644 --- a/tools/vr/src/vk_layer_droiddeck_vr.c +++ b/tools/vr/src/vk_layer_droiddeck_vr.c @@ -30,13 +30,20 @@ static void say(const char *fmt, const char *arg) { static void crash_handler(int sig, siginfo_t *si, void *uc_) { ucontext_t *uc = uc_; +#if defined(__aarch64__) + void *program_counter = (void *)uc->uc_mcontext.pc; +#elif defined(__x86_64__) + void *program_counter = (void *)uc->uc_mcontext.gregs[REG_RIP]; +#else + void *program_counter = NULL; +#endif char buf[512]; int n = snprintf(buf, sizeof(buf), "\n[crashtrace] %s: signal %d addr %p pc %p\n", program_invocation_short_name, sig, - si->si_addr, (void *)uc->uc_mcontext.pc); + si->si_addr, program_counter); if (write(2, buf, n)) {} void *frames[48]; int count = backtrace(frames, 48); - if (count > 1) frames[1] = (void *)uc->uc_mcontext.pc; + if (count > 1) frames[1] = program_counter; for (int i = 1; i < count; i++) { Dl_info info; if (dladdr(frames[i], &info) && info.dli_fname) @@ -477,7 +484,6 @@ struct bridged { int fd; int timeline; unsigned long ino; - uint64_t submitted; }; static pthread_mutex_t g_br_lock = PTHREAD_MUTEX_INITIALIZER; static struct bridged g_br[MAX_SEMS]; @@ -597,7 +603,10 @@ static VkResult VKAPI_CALL CreateSemaphore(VkDevice d, const VkSemaphoreCreateIn if (timeline) timeline_note(*out, 1); return r; } - if (!export_info) return r; + if (!export_info || !timeline || + ((const VkExportSemaphoreCreateInfo *)export_info)->handleTypes != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT) return r; + for (const VkBaseInStructure *item = ci->pNext; item; item = item->pNext) + if (item != export_info && item != (const void *)type_info) return r; VkSemaphoreTypeCreateInfo type = {0}; VkSemaphoreCreateInfo mine = *ci; mine.pNext = NULL; @@ -612,13 +621,26 @@ static VkResult VKAPI_CALL CreateSemaphore(VkDevice d, const VkSemaphoreCreateIn int fd = memfd_create(SHARED_NAME, MFD_CLOEXEC); _Atomic uint64_t *counter = NULL; if (fd >= 0 && ftruncate(fd, 4096) == 0) counter = map_counter(fd); - if (!counter) { if (fd >= 0) close(fd); return r; } + if (!counter) { + if (fd >= 0) close(fd); + NEXT(d, DestroySemaphore)(d, *out, a); + timeline_note(*out, 0); + *out = VK_NULL_HANDLE; + return VK_ERROR_OUT_OF_HOST_MEMORY; + } atomic_store(counter, timeline ? type_info->initialValue : 0); pthread_mutex_lock(&g_br_lock); struct bridged *slot = bridged_find(VK_NULL_HANDLE); - if (slot) *slot = (struct bridged){*out, d, counter, fd, timeline, fd_ino(fd), 0}; + if (slot) *slot = (struct bridged){*out, d, counter, fd, timeline, fd_ino(fd)}; pthread_mutex_unlock(&g_br_lock); - if (!slot) { munmap((void *)counter, 4096); close(fd); return r; } + if (!slot) { + munmap((void *)counter, 4096); + close(fd); + NEXT(d, DestroySemaphore)(d, *out, a); + timeline_note(*out, 0); + *out = VK_NULL_HANDLE; + return VK_ERROR_TOO_MANY_OBJECTS; + } if (timeline) poller_start(); return r; } @@ -646,6 +668,11 @@ static VkResult VKAPI_CALL GetSemaphoreFdKHR(VkDevice d, const VkSemaphoreGetFdI pthread_mutex_lock(&g_br_lock); struct bridged *b = bridged_find(info->semaphore); int mine = b && b->fd >= 0 ? dup(b->fd) : -2; + if (b && info->handleType != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT) { + if (mine >= 0) close(mine); + pthread_mutex_unlock(&g_br_lock); + return VK_ERROR_INVALID_EXTERNAL_HANDLE; + } pthread_mutex_unlock(&g_br_lock); if (mine == -2) return NEXT(d, GetSemaphoreFdKHR)(d, info, fd); *fd = mine; @@ -654,113 +681,29 @@ static VkResult VKAPI_CALL GetSemaphoreFdKHR(VkDevice d, const VkSemaphoreGetFdI static VkResult VKAPI_CALL ImportSemaphoreFdKHR(VkDevice d, const VkImportSemaphoreFdInfoKHR *info) { if (info->fd < 0 || !is_shared_counter(info->fd)) return NEXT(d, ImportSemaphoreFdKHR)(d, info); - _Atomic uint64_t *counter = map_counter(info->fd); - const unsigned long ino = fd_ino(info->fd); - close(info->fd); const int timeline = timeline_known(info->semaphore); + struct stat metadata; + if (!timeline || info->flags || info->handleType != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT || + fstat(info->fd, &metadata) != 0 || metadata.st_size != 4096) return VK_ERROR_INVALID_EXTERNAL_HANDLE; + _Atomic uint64_t *counter = map_counter(info->fd); + if (!counter) return VK_ERROR_OUT_OF_HOST_MEMORY; + const unsigned long ino = fd_ino(info->fd); pthread_mutex_lock(&g_br_lock); struct bridged *b = bridged_find(info->semaphore); if (b) bridged_drop(b); b = bridged_find(VK_NULL_HANDLE); - if (b && counter) *b = (struct bridged){info->semaphore, d, counter, -1, timeline, ino, 0}; - else if (counter) munmap((void *)counter, 4096); + if (b) *b = (struct bridged){info->semaphore, d, counter, info->fd, timeline, ino}; pthread_mutex_unlock(&g_br_lock); + if (!b) { + munmap((void *)counter, 4096); + return VK_ERROR_TOO_MANY_OBJECTS; + } if (timeline) poller_start(); static int said; - if (said++ < 4) say(timeline ? "imported a shared timeline semaphore (%s): bridged" : "imported a shared binary semaphore (%s): its waits are dropped", - program_invocation_short_name); + if (said++ < 4) say("imported a shared timeline semaphore (%s): waits retained", program_invocation_short_name); return VK_SUCCESS; } -static int drop_wait(VkSemaphore s) { - pthread_mutex_lock(&g_br_lock); - struct bridged *b = bridged_find(s); - int r = b && !b->timeline && b->fd < 0; - pthread_mutex_unlock(&g_br_lock); - return r; -} - -static uint64_t now_us(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000000u + (uint64_t)ts.tv_nsec / 1000u; -} - -#define SUBMIT_GRACE_US 3000 - -static int submit_drop_wait(VkSemaphore s, uint64_t value) { - pthread_mutex_lock(&g_br_lock); - struct bridged *b = bridged_find(s); - if (!b) { pthread_mutex_unlock(&g_br_lock); return 0; } - if (!b->timeline) { int r = b->fd < 0; pthread_mutex_unlock(&g_br_lock); return r; } - const int imported = b->fd < 0; - const uint64_t queued = b->submitted; - VkDevice dev = b->dev; - _Atomic uint64_t *counter = b->counter; - pthread_mutex_unlock(&g_br_lock); - if (!imported || !value || value <= queued) return 0; - struct dev *d = dev_for(dev); - if (!d) return 0; - PFN_vkGetSemaphoreCounterValue get = (PFN_vkGetSemaphoreCounterValue)d->gdpa(dev, "vkGetSemaphoreCounterValue"); - if (!get) get = (PFN_vkGetSemaphoreCounterValue)d->gdpa(dev, "vkGetSemaphoreCounterValueKHR"); - PFN_vkSignalSemaphore sig = (PFN_vkSignalSemaphore)d->gdpa(dev, "vkSignalSemaphore"); - if (!sig) sig = (PFN_vkSignalSemaphore)d->gdpa(dev, "vkSignalSemaphoreKHR"); - if (!get) return 0; - const uint64_t until = now_us() + SUBMIT_GRACE_US; - for (;;) { - uint64_t local = 0; - if (get(dev, s, &local) == VK_SUCCESS && local >= value) return 0; - uint64_t shared = atomic_load(counter); - if (shared >= value && sig) { - VkSemaphoreSignalInfo si = {VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, NULL, s, shared}; - if (sig(dev, &si) == VK_SUCCESS) return 0; - } - if (now_us() >= until) break; - usleep(100); - } - static _Atomic int dropped; - if (atomic_fetch_add(&dropped, 1) == 0) - say("%s: dropping a submit's wait on a shared value nobody has signalled yet (Turnip would block the submit)", - program_invocation_short_name); - return 1; -} - -static void note_signal(VkSemaphore s, uint64_t value) { - pthread_mutex_lock(&g_br_lock); - struct bridged *b = bridged_find(s); - if (b && b->timeline && value > b->submitted) b->submitted = value; - pthread_mutex_unlock(&g_br_lock); -} - -static void note_submit_signals(uint32_t count, const VkSubmitInfo *submits) { - for (uint32_t i = 0; i < count; i++) { - const VkTimelineSemaphoreSubmitInfo *t = NULL; - for (const VkBaseInStructure *p = submits[i].pNext; p; p = p->pNext) - if (p->sType == VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO) t = (const void *)p; - for (uint32_t j = 0; t && j < submits[i].signalSemaphoreCount && j < t->signalSemaphoreValueCount; j++) - note_signal(submits[i].pSignalSemaphores[j], t->pSignalSemaphoreValues[j]); - } -} - -static void note_submit2_signals(uint32_t count, const VkSubmitInfo2 *submits) { - for (uint32_t i = 0; i < count; i++) - for (uint32_t j = 0; j < submits[i].signalSemaphoreInfoCount; j++) - note_signal(submits[i].pSignalSemaphoreInfos[j].semaphore, submits[i].pSignalSemaphoreInfos[j].value); -} - -static uint32_t keep_waits(const VkSemaphore *in, const VkPipelineStageFlags *stages, const uint64_t *values, uint32_t n, - VkSemaphore *out, VkPipelineStageFlags *out_stages, uint64_t *out_values) { - uint32_t k = 0; - for (uint32_t i = 0; i < n; i++) { - if (submit_drop_wait(in[i], values ? values[i] : 0)) continue; - out[k] = in[i]; - if (stages) out_stages[k] = stages[i]; - if (values) out_values[k] = values[i]; - k++; - } - return k; -} - static void trace_sem(const char *what, VkSemaphore sem, uint64_t value) { if (!sem_trace_on() || value > 3) return; pthread_mutex_lock(&g_br_lock); @@ -786,55 +729,7 @@ static VkResult VKAPI_CALL QueueSubmit(VkQueue q, uint32_t count, const VkSubmit for (uint32_t j = 0; j < submits[i].signalSemaphoreCount; j++) trace_sem("signals", submits[i].pSignalSemaphores[j], t && j < t->signalSemaphoreValueCount ? t->pSignalSemaphoreValues[j] : 0); } - int any = 0; - for (uint32_t i = 0; i < count && !any; i++) { - const VkTimelineSemaphoreSubmitInfo *t = NULL; - for (const VkBaseInStructure *p = submits[i].pNext; p; p = p->pNext) - if (p->sType == VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO) t = (const void *)p; - for (uint32_t j = 0; j < submits[i].waitSemaphoreCount && !any; j++) - any = submit_drop_wait(submits[i].pWaitSemaphores[j], t && j < t->waitSemaphoreValueCount ? t->pWaitSemaphoreValues[j] : 0); - } - if (!any) { - VkResult r = next(q, count, submits, fence); - note_submit_signals(count, submits); - return r; - } - VkSubmitInfo *copy = calloc(count, sizeof(*copy)); - VkTimelineSemaphoreSubmitInfo *tl = calloc(count, sizeof(*tl)); - void **bufs = calloc(count * 3, sizeof(void *)); - for (uint32_t i = 0; i < count; i++) { - copy[i] = submits[i]; - uint32_t n = submits[i].waitSemaphoreCount; - if (!n) continue; - VkSemaphore *sems = calloc(n, sizeof(*sems)); - VkPipelineStageFlags *stages = calloc(n, sizeof(*stages)); - uint64_t *vals = calloc(n, sizeof(*vals)); - bufs[i * 3] = sems; bufs[i * 3 + 1] = stages; bufs[i * 3 + 2] = vals; - const VkTimelineSemaphoreSubmitInfo *t = NULL; - for (const VkBaseInStructure *p = submits[i].pNext; p; p = p->pNext) - if (p->sType == VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO) t = (const void *)p; - uint32_t k = keep_waits(submits[i].pWaitSemaphores, submits[i].pWaitDstStageMask, - t && t->waitSemaphoreValueCount ? t->pWaitSemaphoreValues : NULL, n, sems, stages, vals); - copy[i].waitSemaphoreCount = k; - copy[i].pWaitSemaphores = sems; - copy[i].pWaitDstStageMask = stages; - if (t) { - tl[i] = *t; - tl[i].waitSemaphoreValueCount = t->waitSemaphoreValueCount ? k : 0; - tl[i].pWaitSemaphoreValues = vals; - const void *rest = NULL; - const VkBaseInStructure *prev = NULL; - for (const VkBaseInStructure *p = submits[i].pNext; p; prev = p, p = p->pNext) if (p == (const void *)t) { rest = p->pNext; break; } - (void)prev; - tl[i].pNext = rest; - copy[i].pNext = &tl[i]; - } - } - VkResult r = next(q, count, copy, fence); - note_submit_signals(count, submits); - for (uint32_t i = 0; i < count * 3; i++) free(bufs[i]); - free(bufs); free(tl); free(copy); - return r; + return next(q, count, submits, fence); } static VkResult VKAPI_CALL QueueSubmit2(VkQueue q, uint32_t count, const VkSubmitInfo2 *submits, VkFence fence) { @@ -847,47 +742,13 @@ static VkResult VKAPI_CALL QueueSubmit2(VkQueue q, uint32_t count, const VkSubmi for (uint32_t j = 0; j < submits[i].signalSemaphoreInfoCount; j++) trace_sem("signals", submits[i].pSignalSemaphoreInfos[j].semaphore, submits[i].pSignalSemaphoreInfos[j].value); } - int any = 0; - for (uint32_t i = 0; i < count && !any; i++) - for (uint32_t j = 0; j < submits[i].waitSemaphoreInfoCount && !any; j++) - any = submit_drop_wait(submits[i].pWaitSemaphoreInfos[j].semaphore, submits[i].pWaitSemaphoreInfos[j].value); - if (!any) { - VkResult r = next(q, count, submits, fence); - note_submit2_signals(count, submits); - return r; - } - VkSubmitInfo2 *copy = calloc(count, sizeof(*copy)); - VkSemaphoreSubmitInfo **waits = calloc(count, sizeof(*waits)); - for (uint32_t i = 0; i < count; i++) { - copy[i] = submits[i]; - uint32_t n = submits[i].waitSemaphoreInfoCount, k = 0; - waits[i] = calloc(n + 1, sizeof(VkSemaphoreSubmitInfo)); - for (uint32_t j = 0; j < n; j++) - if (!submit_drop_wait(submits[i].pWaitSemaphoreInfos[j].semaphore, submits[i].pWaitSemaphoreInfos[j].value)) - waits[i][k++] = submits[i].pWaitSemaphoreInfos[j]; - copy[i].waitSemaphoreInfoCount = k; - copy[i].pWaitSemaphoreInfos = waits[i]; - } - VkResult r = next(q, count, copy, fence); - note_submit2_signals(count, submits); - for (uint32_t i = 0; i < count; i++) free(waits[i]); - free(waits); free(copy); - return r; + return next(q, count, submits, fence); } static VkResult VKAPI_CALL WaitSemaphores(VkDevice d, const VkSemaphoreWaitInfo *info, uint64_t timeout) { PFN_vkWaitSemaphores next = (PFN_vkWaitSemaphores)dev_for(d)->gdpa(d, "vkWaitSemaphores"); if (!next) next = (PFN_vkWaitSemaphores)dev_for(d)->gdpa(d, "vkWaitSemaphoresKHR"); - VkSemaphore sems[64]; uint64_t vals[64]; uint32_t k = 0; - for (uint32_t i = 0; i < info->semaphoreCount && k < 64; i++) - if (!drop_wait(info->pSemaphores[i])) { sems[k] = info->pSemaphores[i]; vals[k] = info->pValues[i]; k++; } - if (k == info->semaphoreCount) { - return next(d, info, timeout); - } - if (!k) return VK_SUCCESS; - VkSemaphoreWaitInfo mine = *info; - mine.semaphoreCount = k; mine.pSemaphores = sems; mine.pValues = vals; - return next(d, &mine, timeout); + return next(d, info, timeout); } static VkResult VKAPI_CALL GetSemaphoreCounterValue(VkDevice d, VkSemaphore s, uint64_t *value) { diff --git a/tools/vr/tests/test_semaphore_layer.c b/tools/vr/tests/test_semaphore_layer.c new file mode 100644 index 000000000..1b02e0d85 --- /dev/null +++ b/tools/vr/tests/test_semaphore_layer.c @@ -0,0 +1,130 @@ +#define _GNU_SOURCE +#include +#include +#include "../src/vk_layer_droiddeck_vr.c" + +static const VkSubmitInfo *expected_submit; +static const VkSubmitInfo2 *expected_submit2; +static const VkSemaphoreWaitInfo *expected_wait; +static unsigned create_calls; +static unsigned destroyed; +static VkResult submit_result = VK_SUCCESS; + +static VkResult VKAPI_CALL mock_submit(VkQueue queue, uint32_t count, const VkSubmitInfo *submits, VkFence fence) { + assert(queue && count == 1 && fence == VK_NULL_HANDLE); + assert(submits == expected_submit); + assert(submits->waitSemaphoreCount == 1); + return submit_result; +} + +static VkResult VKAPI_CALL mock_submit2(VkQueue queue, uint32_t count, const VkSubmitInfo2 *submits, VkFence fence) { + assert(queue && count == 1 && fence == VK_NULL_HANDLE); + assert(submits == expected_submit2); + assert(submits->waitSemaphoreInfoCount == 1); + return submit_result; +} + +static VkResult VKAPI_CALL mock_wait(VkDevice device, const VkSemaphoreWaitInfo *info, uint64_t timeout) { + assert(device && info == expected_wait && timeout == 7654321); + assert(info->semaphoreCount == 65); + return VK_TIMEOUT; +} + +static VkResult VKAPI_CALL mock_create(VkDevice device, const VkSemaphoreCreateInfo *info, + const VkAllocationCallbacks *allocator, VkSemaphore *output) { + (void)device; + (void)allocator; + create_calls++; + for (const VkBaseInStructure *item = info->pNext; item; item = item->pNext) + if (item->sType == VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO) return VK_ERROR_INVALID_EXTERNAL_HANDLE; + *output = (VkSemaphore)(uintptr_t)(create_calls + 100); + return VK_SUCCESS; +} + +static void VKAPI_CALL mock_destroy(VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks *allocator) { + (void)device; + (void)semaphore; + (void)allocator; + destroyed++; +} + +static PFN_vkVoidFunction VKAPI_CALL mock_proc(VkDevice device, const char *name) { + (void)device; + if (!strcmp(name, "vkQueueSubmit")) return (PFN_vkVoidFunction)mock_submit; + if (!strcmp(name, "vkQueueSubmit2")) return (PFN_vkVoidFunction)mock_submit2; + if (!strcmp(name, "vkWaitSemaphores")) return (PFN_vkVoidFunction)mock_wait; + if (!strcmp(name, "vkCreateSemaphore")) return (PFN_vkVoidFunction)mock_create; + if (!strcmp(name, "vkDestroySemaphore")) return (PFN_vkVoidFunction)mock_destroy; + assert(0); + return NULL; +} + +int main(void) { + setenv("DROIDDECK_VR_LAYER_QUIET", "1", 1); + void *dispatch = &dispatch; + VkDevice device = (VkDevice)&dispatch; + VkQueue queue = (VkQueue)&dispatch; + g_dev[0] = (struct dev){device, mock_proc, NULL, 1, 0}; + g_poller_started = 1; + VkExportSemaphoreCreateInfo export_info = {VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, NULL, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT}; + VkSemaphoreCreateInfo create = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &export_info, 0}; + VkSemaphore semaphore = VK_NULL_HANDLE; + assert(CreateSemaphore(device, &create, NULL, &semaphore) == VK_ERROR_INVALID_EXTERNAL_HANDLE); + assert(create_calls == 1 && !semaphore); + VkSemaphoreTypeCreateInfo type = {VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, &export_info, + VK_SEMAPHORE_TYPE_TIMELINE, 0}; + create.pNext = &type; + assert(CreateSemaphore(device, &create, NULL, &semaphore) == VK_SUCCESS); + struct bridged *bridge = bridged_find(semaphore); + assert(bridge && atomic_load(bridge->counter) == 0); + VkPipelineStageFlags stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + uint64_t value = 9; + VkTimelineSemaphoreSubmitInfo timeline = {VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, NULL, 1, &value, 0, NULL}; + VkSubmitInfo submit = {VK_STRUCTURE_TYPE_SUBMIT_INFO, &timeline, 1, &semaphore, &stage, 0, NULL, 0, NULL}; + expected_submit = &submit; + assert(QueueSubmit(queue, 1, &submit, VK_NULL_HANDLE) == VK_SUCCESS); + assert(atomic_load(bridge->counter) == 0); + VkSemaphoreSubmitInfo wait2 = {VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, NULL, semaphore, value, + VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, 0}; + VkSubmitInfo2 submit2 = {VK_STRUCTURE_TYPE_SUBMIT_INFO_2, NULL, 0, 1, &wait2, 0, NULL, 0, NULL}; + expected_submit2 = &submit2; + assert(QueueSubmit2(queue, 1, &submit2, VK_NULL_HANDLE) == VK_SUCCESS); + submit_result = VK_ERROR_DEVICE_LOST; + assert(QueueSubmit(queue, 1, &submit, VK_NULL_HANDLE) == VK_ERROR_DEVICE_LOST); + assert(QueueSubmit2(queue, 1, &submit2, VK_NULL_HANDLE) == VK_ERROR_DEVICE_LOST); + VkSemaphore semaphores[65]; + uint64_t values[65]; + for (unsigned index = 0; index < 65; index++) { semaphores[index] = semaphore; values[index] = value; } + VkSemaphoreWaitInfo wait = {VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, NULL, VK_SEMAPHORE_WAIT_ANY_BIT, 65, semaphores, values}; + expected_wait = &wait; + assert(WaitSemaphores(device, &wait, 7654321) == VK_TIMEOUT); + int descriptor = dup(bridge->fd); + VkSemaphore binary = (VkSemaphore)(uintptr_t)999; + VkImportSemaphoreFdInfoKHR import_info = {VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, NULL, binary, 0, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, descriptor}; + assert(ImportSemaphoreFdKHR(device, &import_info) == VK_ERROR_INVALID_EXTERNAL_HANDLE); + assert(fcntl(descriptor, F_GETFD) >= 0); + timeline_note(binary, 1); + assert(ftruncate(descriptor, 32) == 0); + assert(ImportSemaphoreFdKHR(device, &import_info) == VK_ERROR_INVALID_EXTERNAL_HANDLE); + assert(ftruncate(descriptor, 4096) == 0); + import_info.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT; + assert(ImportSemaphoreFdKHR(device, &import_info) == VK_ERROR_INVALID_EXTERNAL_HANDLE); + import_info.flags = 0; + assert(ImportSemaphoreFdKHR(device, &import_info) == VK_SUCCESS); + VkSemaphoreGetFdInfoKHR get_info = {VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, NULL, binary, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT}; + int reexported = -1; + assert(GetSemaphoreFdKHR(device, &get_info, &reexported) == VK_SUCCESS && reexported >= 0); + assert(fd_ino(reexported) == fd_ino(descriptor)); + close(reexported); + get_info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + assert(GetSemaphoreFdKHR(device, &get_info, &reexported) == VK_ERROR_INVALID_EXTERNAL_HANDLE); + DestroySemaphore(device, binary, NULL); + assert(fcntl(descriptor, F_GETFD) == -1 && errno == EBADF); + DestroySemaphore(device, semaphore, NULL); + assert(destroyed == 2 && !bridged_find(semaphore)); + puts("semaphore layer: pending waits retained, errors propagated, unsupported binary imports refused, fd ownership passed"); + return 0; +}