fix: retain Vulkan semaphore dependencies

This commit is contained in:
XxJSONDeruloxX committed 2026-10-14 21:52:55 -04:00
1 parent 9b91c02e07
commit bb58d0cc7b
4 files changed
+218 -190

No files matched your search

+13 -2
View File
@@ -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
+26
View File
@@ -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)
+49 -188
View File
@@ -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) {
+130
View File
@@ -0,0 +1,130 @@
#define _GNU_SOURCE
#include <assert.h>
#include <fcntl.h>
#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;
}