[PATCH vkd3d v3 2/4] vkd3d: Map timeline semaphore values to fence virtual values and buffer out-of-order waits.

Conor McCarthy cmccarthy at codeweavers.com
Thu May 12 10:11:06 CDT 2022


Strictly increasing timeline values must be mapped to fence virtual values
to avoid invalid use of Vulkan timeline semaphores. In particular, non-
increasing values and value jumps of >= 4G are permitted in d3d12.

Different virtual D3D12 command queues may map to the same Vulkan queue.
If a wait of value N is submitted on one command queue, and then a signal
for >= N is submitted on another, but they are sent to the same Vk queue,
the wait will never complete. The solution is to buffer out-of-order waits
and any subsequent queue commands until an unblocking signal value is
submitted to a different D3D12 queue, or signaled on the CPU.

Buffering out-of-order waits also fixes the old fence implementation so it
is fully functional, though a bit less efficient than timeline semaphores.

Based in part on vkd3d-proton patches by Hans-Kristian Arntzen. Unlike the
vkd3d-proton implementation, this patch does not use worker threads for
submissions to the Vulkan queue.

Signed-off-by: Conor McCarthy <cmccarthy at codeweavers.com>
---
 libs/vkd3d/command.c       | 894 ++++++++++++++++++++++---------------
 libs/vkd3d/device.c        |  81 +---
 libs/vkd3d/vkd3d_private.h |  78 +++-
 tests/d3d12.c              |   4 +-
 4 files changed, 602 insertions(+), 455 deletions(-)

diff --git a/libs/vkd3d/command.c b/libs/vkd3d/command.c
index b187c65b..d0782e5a 100644
--- a/libs/vkd3d/command.c
+++ b/libs/vkd3d/command.c
@@ -22,7 +22,11 @@
 
 static void d3d12_fence_incref(struct d3d12_fence *fence);
 static void d3d12_fence_decref(struct d3d12_fence *fence);
-static HRESULT d3d12_fence_signal(struct d3d12_fence *fence, uint64_t value, VkFence vk_fence);
+static HRESULT d3d12_fence_signal(struct d3d12_fence *fence, uint64_t value, VkFence vk_fence, bool on_cpu);
+static void d3d12_fence_signal_timeline_semaphore(struct d3d12_fence *fence, uint64_t timeline_value);
+static HRESULT d3d12_command_queue_signal(struct d3d12_command_queue *command_queue,
+        struct d3d12_fence *fence, uint64_t value);
+static bool d3d12_command_queue_flush_ops(struct d3d12_command_queue *queue, bool *flushed_any);
 
 HRESULT vkd3d_queue_create(struct d3d12_device *device,
         uint32_t family_index, const VkQueueFamilyProperties *properties, struct vkd3d_queue **queue)
@@ -48,9 +52,6 @@ HRESULT vkd3d_queue_create(struct d3d12_device *device,
     object->vk_queue_flags = properties->queueFlags;
     object->timestamp_bits = properties->timestampValidBits;
 
-    object->wait_completion_semaphore = VK_NULL_HANDLE;
-    object->pending_wait_completion_value = 0;
-
     object->semaphores = NULL;
     object->semaphores_size = 0;
     object->semaphore_count = 0;
@@ -66,20 +67,6 @@ HRESULT vkd3d_queue_create(struct d3d12_device *device,
     return S_OK;
 }
 
-bool vkd3d_queue_init_timeline_semaphore(struct vkd3d_queue *queue, struct d3d12_device *device)
-{
-    VkResult vr;
-
-    if (!queue->wait_completion_semaphore
-            && (vr = vkd3d_create_timeline_semaphore(device, 0, &queue->wait_completion_semaphore)) < 0)
-    {
-        WARN("Failed to create timeline semaphore, vr %d.\n", vr);
-        return false;
-    }
-
-    return true;
-}
-
 void vkd3d_queue_destroy(struct vkd3d_queue *queue, struct d3d12_device *device)
 {
     const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
@@ -94,8 +81,6 @@ void vkd3d_queue_destroy(struct vkd3d_queue *queue, struct d3d12_device *device)
 
     vkd3d_free(queue->semaphores);
 
-    VK_CALL(vkDestroySemaphore(device->vk_device, queue->wait_completion_semaphore, NULL));
-
     for (i = 0; i < ARRAY_SIZE(queue->old_vk_semaphores); ++i)
     {
         if (queue->old_vk_semaphores[i])
@@ -308,9 +293,7 @@ static void vkd3d_wait_for_gpu_timeline_semaphore(struct vkd3d_fence_worker *wor
     const struct d3d12_device *device = worker->device;
     const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
     VkSemaphoreWaitInfoKHR wait_info;
-    uint64_t counter_value;
     VkResult vr;
-    HRESULT hr;
 
     wait_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR;
     wait_info.pNext = NULL;
@@ -328,19 +311,10 @@ static void vkd3d_wait_for_gpu_timeline_semaphore(struct vkd3d_fence_worker *wor
         return;
     }
 
-    if ((vr = VK_CALL(vkGetSemaphoreCounterValueKHR(device->vk_device, waiting_fence->u.vk_semaphore,
-            &counter_value))) < 0)
-    {
-        ERR("Failed to get Vulkan semaphore value, vr %d.\n", vr);
-    }
-    else
-    {
-        TRACE("Signaling fence %p value %#"PRIx64".\n", waiting_fence->fence, waiting_fence->value);
-        if (FAILED(hr = d3d12_fence_signal(waiting_fence->fence, counter_value, VK_NULL_HANDLE)))
-            ERR("Failed to signal D3D12 fence, hr %#x.\n", hr);
+    TRACE("Signaling fence %p value %#"PRIx64".\n", waiting_fence->fence, waiting_fence->value);
+    d3d12_fence_signal_timeline_semaphore(waiting_fence->fence, waiting_fence->value);
 
-        d3d12_fence_decref(waiting_fence->fence);
-    }
+    d3d12_fence_decref(waiting_fence->fence);
 }
 
 static void vkd3d_wait_for_gpu_fence(struct vkd3d_fence_worker *worker,
@@ -361,7 +335,7 @@ static void vkd3d_wait_for_gpu_fence(struct vkd3d_fence_worker *worker,
     }
 
     TRACE("Signaling fence %p value %#"PRIx64".\n", waiting_fence->fence, waiting_fence->value);
-    if (FAILED(hr = d3d12_fence_signal(waiting_fence->fence, waiting_fence->value, waiting_fence->u.vk_fence)))
+    if (FAILED(hr = d3d12_fence_signal(waiting_fence->fence, waiting_fence->value, waiting_fence->u.vk_fence, false)))
         ERR("Failed to signal D3D12 fence, hr %#x.\n", hr);
 
     d3d12_fence_decref(waiting_fence->fence);
@@ -434,7 +408,7 @@ static HRESULT vkd3d_fence_worker_start(struct vkd3d_fence_worker *worker,
     worker->fences = NULL;
     worker->fences_size = 0;
 
-    worker->wait_for_gpu_fence = device->use_timeline_semaphores
+    worker->wait_for_gpu_fence = device->vk_info.KHR_timeline_semaphore
             ? vkd3d_wait_for_gpu_timeline_semaphore : vkd3d_wait_for_gpu_fence;
 
     if ((rc = vkd3d_mutex_init(&worker->mutex)))
@@ -606,17 +580,17 @@ static void d3d12_fence_garbage_collect_vk_semaphores_locked(struct d3d12_fence
         current = &fence->semaphores[i];
         /* The semaphore doesn't have a pending signal operation if the fence
          * was signaled. */
-        if ((current->vk_fence || current->is_acquired) && !destroy_all)
+        if ((current->u.binary.vk_fence || current->u.binary.is_acquired) && !destroy_all)
         {
             ++i;
             continue;
         }
 
-        if (current->vk_fence)
+        if (current->u.binary.vk_fence)
             WARN("Destroying potentially pending semaphore.\n");
-        assert(!current->is_acquired);
+        assert(!current->u.binary.is_acquired);
 
-        VK_CALL(vkDestroySemaphore(device->vk_device, current->vk_semaphore, NULL));
+        VK_CALL(vkDestroySemaphore(device->vk_device, current->u.binary.vk_semaphore, NULL));
         fence->semaphores[i] = fence->semaphores[--fence->semaphore_count];
     }
 
@@ -652,23 +626,16 @@ static void d3d12_fence_destroy_vk_objects(struct d3d12_fence *fence)
     vkd3d_mutex_unlock(&fence->mutex);
 }
 
-static struct vkd3d_signaled_semaphore *d3d12_fence_acquire_vk_semaphore(struct d3d12_fence *fence,
+static struct vkd3d_signaled_semaphore *d3d12_fence_acquire_vk_semaphore_locked(struct d3d12_fence *fence,
         uint64_t value, uint64_t *completed_value)
 {
     struct vkd3d_signaled_semaphore *semaphore;
     struct vkd3d_signaled_semaphore *current;
     uint64_t semaphore_value;
     unsigned int i;
-    int rc;
 
     TRACE("fence %p, value %#"PRIx64".\n", fence, value);
 
-    if ((rc = vkd3d_mutex_lock(&fence->mutex)))
-    {
-        ERR("Failed to lock mutex, error %d.\n", rc);
-        return VK_NULL_HANDLE;
-    }
-
     semaphore = NULL;
     semaphore_value = ~(uint64_t)0;
 
@@ -676,7 +643,7 @@ static struct vkd3d_signaled_semaphore *d3d12_fence_acquire_vk_semaphore(struct
     {
         current = &fence->semaphores[i];
         /* Prefer a semaphore with the smallest value. */
-        if (!current->is_acquired && current->value >= value && semaphore_value >= current->value)
+        if (!current->u.binary.is_acquired && current->value >= value && semaphore_value >= current->value)
         {
             semaphore = current;
             semaphore_value = current->value;
@@ -686,12 +653,10 @@ static struct vkd3d_signaled_semaphore *d3d12_fence_acquire_vk_semaphore(struct
     }
 
     if (semaphore)
-        semaphore->is_acquired = true;
+        semaphore->u.binary.is_acquired = true;
 
     *completed_value = fence->value;
 
-    vkd3d_mutex_unlock(&fence->mutex);
-
     return semaphore;
 }
 
@@ -705,7 +670,7 @@ static void d3d12_fence_remove_vk_semaphore(struct d3d12_fence *fence, struct vk
         return;
     }
 
-    assert(semaphore->is_acquired);
+    assert(semaphore->u.binary.is_acquired);
 
     *semaphore = fence->semaphores[--fence->semaphore_count];
 
@@ -722,32 +687,133 @@ static void d3d12_fence_release_vk_semaphore(struct d3d12_fence *fence, struct v
         return;
     }
 
-    assert(semaphore->is_acquired);
-    semaphore->is_acquired = false;
+    assert(semaphore->u.binary.is_acquired);
+    semaphore->u.binary.is_acquired = false;
 
     vkd3d_mutex_unlock(&fence->mutex);
 }
 
-static HRESULT d3d12_fence_add_vk_semaphore(struct d3d12_fence *fence,
-        VkSemaphore vk_semaphore, VkFence vk_fence, uint64_t value)
+static void d3d12_fence_update_pending_value_locked(struct d3d12_fence *fence)
+{
+    uint64_t new_max_pending_value;
+    unsigned int i;
+
+    for (i = 0, new_max_pending_value = 0; i < fence->semaphore_count; ++i)
+        new_max_pending_value = max(fence->semaphores[i].value, new_max_pending_value);
+
+    fence->max_pending_value = max(fence->value, new_max_pending_value);
+}
+
+static HRESULT d3d12_fence_update_pending_value(struct d3d12_fence *fence)
+{
+    int rc;
+
+    if ((rc = vkd3d_mutex_lock(&fence->mutex)))
+    {
+        ERR("Failed to lock mutex, error %d.\n", rc);
+        return hresult_from_errno(rc);
+    }
+
+    d3d12_fence_update_pending_value_locked(fence);
+
+    vkd3d_mutex_unlock(&fence->mutex);
+
+    return S_OK;
+}
+
+static HRESULT d3d12_device_add_blocked_command_queues(struct d3d12_device *device,
+        struct d3d12_command_queue * const *command_queues, unsigned int count)
 {
-    struct vkd3d_signaled_semaphore *semaphore;
     HRESULT hr = S_OK;
+    unsigned int i;
     int rc;
 
-    TRACE("fence %p, value %#"PRIx64".\n", fence, value);
+    if ((rc = vkd3d_mutex_lock(&device->mutex)))
+    {
+        ERR("Failed to lock mutex, error %d.\n", rc);
+        return hresult_from_errno(rc);
+    }
 
-    if (!(semaphore = vkd3d_malloc(sizeof(*semaphore))))
+    if ((i = ARRAY_SIZE(device->blocked_queues) - device->blocked_queue_count) < count)
     {
-        ERR("Failed to add semaphore.\n");
-        return E_OUTOFMEMORY;
+        FIXME("Failed to add %u blocked command queue(s) to device %p.\n", count - i, device);
+        count = i;
+        hr = E_FAIL;
+    }
+
+    for (i = 0; i < count; ++i)
+        device->blocked_queues[device->blocked_queue_count++] = command_queues[i];
+
+    vkd3d_mutex_unlock(&device->mutex);
+    return hr;
+}
+
+static HRESULT d3d12_device_flush_blocked_queues_once(struct d3d12_device *device, bool *flushed_any)
+{
+    struct d3d12_command_queue *blocked_queues[VKD3D_MAX_DEVICE_BLOCKED_QUEUES];
+    unsigned int i, blocked_queue_count;
+    int rc;
+
+    *flushed_any = false;
+
+    if ((rc = vkd3d_mutex_lock(&device->mutex)))
+    {
+        ERR("Failed to lock mutex, error %d.\n", rc);
+        return hresult_from_errno(rc);
+    }
+
+    /* Flush any ops unblocked by a new pending value. These cannot be flushed
+     * with the device locked, so move the queue pointers to a local array. */
+    blocked_queue_count = device->blocked_queue_count;
+    memcpy(blocked_queues, device->blocked_queues, blocked_queue_count * sizeof(blocked_queues[0]));
+    device->blocked_queue_count = 0;
+
+    vkd3d_mutex_unlock(&device->mutex);
+
+    i = 0;
+    while (i < blocked_queue_count)
+    {
+        if (d3d12_command_queue_flush_ops(blocked_queues[i], flushed_any))
+            blocked_queues[i] = blocked_queues[--blocked_queue_count];
+        else
+            ++i;
+    }
+
+    /* None of these queues could have been re-added during the above loop because
+     * blocked queues always have a nonzero op count. */
+    return d3d12_device_add_blocked_command_queues(device, blocked_queues, blocked_queue_count);
+}
+
+static HRESULT d3d12_device_flush_blocked_queues(struct d3d12_device *device)
+{
+    bool flushed_any;
+    HRESULT hr;
+
+    /* Executing an op on one queue may unblock another, so repeat until nothing is flushed. */
+    do
+    {
+        if (!device->blocked_queue_count)
+            return S_OK;
+        if (FAILED(hr = d3d12_device_flush_blocked_queues_once(device, &flushed_any)))
+            return hr;
     }
+    while (flushed_any);
+
+    return S_OK;
+}
+
+static HRESULT d3d12_fence_add_vk_semaphore(struct d3d12_fence *fence, VkSemaphore vk_semaphore,
+        VkFence vk_fence, uint64_t value, const struct vkd3d_queue *signalling_queue)
+{
+    struct vkd3d_signaled_semaphore *semaphore;
+    int rc;
+
+    TRACE("fence %p, value %#"PRIx64".\n", fence, value);
 
     if ((rc = vkd3d_mutex_lock(&fence->mutex)))
     {
         ERR("Failed to lock mutex, error %d.\n", rc);
-        vkd3d_free(semaphore);
-        return E_FAIL;
+        return hresult_from_errno(rc);
     }
 
     d3d12_fence_garbage_collect_vk_semaphores_locked(fence, false);
@@ -757,21 +823,24 @@ static HRESULT d3d12_fence_add_vk_semaphore(struct d3d12_fence *fence,
     {
         ERR("Failed to add semaphore.\n");
         vkd3d_mutex_unlock(&fence->mutex);
-        return false;
+        return E_OUTOFMEMORY;
     }
 
     semaphore = &fence->semaphores[fence->semaphore_count++];
     semaphore->value = value;
-    semaphore->vk_semaphore = vk_semaphore;
-    semaphore->vk_fence = vk_fence;
-    semaphore->is_acquired = false;
+    semaphore->u.binary.vk_semaphore = vk_semaphore;
+    semaphore->u.binary.vk_fence = vk_fence;
+    semaphore->u.binary.is_acquired = false;
+    semaphore->signalling_queue = signalling_queue;
+
+    d3d12_fence_update_pending_value_locked(fence);
 
     vkd3d_mutex_unlock(&fence->mutex);
 
-    return hr;
+    return d3d12_device_flush_blocked_queues(fence->device);
 }
 
-static bool d3d12_fence_signal_external_events_locked(struct d3d12_fence *fence)
+static void d3d12_fence_signal_external_events_locked(struct d3d12_fence *fence)
 {
     struct d3d12_device *device = fence->device;
     bool signal_null_event_cond = false;
@@ -803,10 +872,11 @@ static bool d3d12_fence_signal_external_events_locked(struct d3d12_fence *fence)
 
     fence->event_count = j;
 
-    return signal_null_event_cond;
+    if (signal_null_event_cond)
+        vkd3d_cond_broadcast(&fence->null_event_cond);
 }
 
-static HRESULT d3d12_fence_signal(struct d3d12_fence *fence, uint64_t value, VkFence vk_fence)
+static HRESULT d3d12_fence_signal(struct d3d12_fence *fence, uint64_t value, VkFence vk_fence, bool on_cpu)
 {
     struct d3d12_device *device = fence->device;
     struct vkd3d_signaled_semaphore *current;
@@ -821,8 +891,7 @@ static HRESULT d3d12_fence_signal(struct d3d12_fence *fence, uint64_t value, VkF
 
     fence->value = value;
 
-    if (d3d12_fence_signal_external_events_locked(fence))
-        vkd3d_cond_broadcast(&fence->null_event_cond);
+    d3d12_fence_signal_external_events_locked(fence);
 
     if (vk_fence)
     {
@@ -831,8 +900,8 @@ static HRESULT d3d12_fence_signal(struct d3d12_fence *fence, uint64_t value, VkF
         for (i = 0; i < fence->semaphore_count; ++i)
         {
             current = &fence->semaphores[i];
-            if (current->vk_fence == vk_fence)
-                current->vk_fence = VK_NULL_HANDLE;
+            if (current->u.binary.vk_fence == vk_fence)
+                current->u.binary.vk_fence = VK_NULL_HANDLE;
         }
 
         for (i = 0; i < ARRAY_SIZE(fence->old_vk_fences); ++i)
@@ -849,9 +918,101 @@ static HRESULT d3d12_fence_signal(struct d3d12_fence *fence, uint64_t value, VkF
             VK_CALL(vkDestroyFence(device->vk_device, vk_fence, NULL));
     }
 
+    d3d12_fence_update_pending_value_locked(fence);
+
     vkd3d_mutex_unlock(&fence->mutex);
 
-    return S_OK;
+    return on_cpu ? d3d12_device_flush_blocked_queues(device) : S_OK;
+}
+
+static uint64_t d3d12_fence_add_pending_timeline_signal(struct d3d12_fence *fence, uint64_t virtual_value,
+        const struct vkd3d_queue *signalling_queue)
+{
+    struct vkd3d_signaled_semaphore *semaphore;
+    int rc;
+
+    if ((rc = vkd3d_mutex_lock(&fence->mutex)))
+    {
+        ERR("Failed to lock mutex, error %d.\n", rc);
+        return hresult_from_errno(rc);
+    }
+
+    if (!vkd3d_array_reserve((void **)&fence->semaphores, &fence->semaphores_size,
+            fence->semaphore_count + 1, sizeof(*fence->semaphores)))
+    {
+        return 0;
+    }
+
+    semaphore = &fence->semaphores[fence->semaphore_count++];
+    semaphore->value = virtual_value;
+    semaphore->u.timeline_value = ++fence->pending_timeline_value;
+    semaphore->signalling_queue = signalling_queue;
+
+    vkd3d_mutex_unlock(&fence->mutex);
+
+    return fence->pending_timeline_value;
+}
+
+static uint64_t d3d12_fence_get_timeline_wait_value_locked(struct d3d12_fence *fence, uint64_t virtual_value)
+{
+    uint64_t target_timeline_value = UINT64_MAX;
+    unsigned int i;
+
+    /* Find the smallest physical value which is at least the virtual value. */
+    for (i = 0; i < fence->semaphore_count; ++i)
+    {
+        if (virtual_value <= fence->semaphores[i].value)
+            target_timeline_value = min(target_timeline_value, fence->semaphores[i].u.timeline_value);
+    }
+
+    /* No timeline value will be found if it was already signaled on the GPU and handled in
+     * the worker thread. A wait must still be emitted as a barrier against command re-ordering. */
+    return (target_timeline_value == UINT64_MAX) ? 0 : target_timeline_value;
+}
+
+static void d3d12_fence_signal_timeline_semaphore(struct d3d12_fence *fence, uint64_t timeline_value)
+{
+    bool did_signal;
+    unsigned int i;
+    int rc;
+
+    if ((rc = vkd3d_mutex_lock(&fence->mutex)))
+    {
+        ERR("Failed to lock mutex, error %d.\n", rc);
+        return;
+    }
+
+    /* With multiple fence workers, it is possible that signal calls are out of
+     * order. The physical value itself is monotonic, but we need to make sure
+     * that all signals happen in correct order if there are fence rewinds.
+     * We don't expect the loop to run more than once, but there might be
+     * extreme edge cases where we signal 2 or more. */
+    while (fence->timeline_value < timeline_value)
+    {
+        ++fence->timeline_value;
+        did_signal = false;
+
+        for (i = 0; i < fence->semaphore_count; ++i)
+        {
+            if (fence->timeline_value == fence->semaphores[i].u.timeline_value)
+            {
+                fence->value = fence->semaphores[i].value;
+                d3d12_fence_signal_external_events_locked(fence);
+                fence->semaphores[i] = fence->semaphores[--fence->semaphore_count];
+                did_signal = true;
+                break;
+            }
+        }
+
+        if (!did_signal)
+            FIXME("Did not signal a virtual value.\n");
+    }
+
+    /* If a rewind remains queued, the virtual value deleted above may be
+     * greater than any pending value, so update the max pending value. */
+    d3d12_fence_update_pending_value_locked(fence);
+
+    vkd3d_mutex_unlock(&fence->mutex);
 }
 
 static HRESULT STDMETHODCALLTYPE d3d12_fence_QueryInterface(ID3D12Fence *iface,
@@ -1060,100 +1221,8 @@ static HRESULT STDMETHODCALLTYPE d3d12_fence_SetEventOnCompletion(ID3D12Fence *i
     return S_OK;
 }
 
-static inline bool d3d12_fence_gpu_wait_is_completed(const struct d3d12_fence *fence, unsigned int i)
-{
-    const struct d3d12_device *device = fence->device;
-    const struct vkd3d_vk_device_procs *vk_procs;
-    uint64_t value;
-    VkResult vr;
-
-    vk_procs = &device->vk_procs;
-
-    if ((vr = VK_CALL(vkGetSemaphoreCounterValueKHR(device->vk_device,
-            fence->gpu_waits[i].queue->wait_completion_semaphore, &value))) >= 0)
-    {
-        return value >= fence->gpu_waits[i].pending_value;
-    }
-
-    ERR("Failed to get Vulkan semaphore status, vr %d.\n", vr);
-    return true;
-}
-
-static inline bool d3d12_fence_has_pending_gpu_ops_locked(struct d3d12_fence *fence)
-{
-    const struct d3d12_device *device = fence->device;
-    const struct vkd3d_vk_device_procs *vk_procs;
-    uint64_t value;
-    unsigned int i;
-    VkResult vr;
-
-    for (i = 0; i < fence->gpu_wait_count; ++i)
-    {
-        if (d3d12_fence_gpu_wait_is_completed(fence, i) && i < --fence->gpu_wait_count)
-            fence->gpu_waits[i] = fence->gpu_waits[fence->gpu_wait_count];
-    }
-    if (fence->gpu_wait_count)
-        return true;
-
-    /* Check for pending signals too. */
-    if (fence->value >= fence->pending_timeline_value)
-        return false;
-
-    vk_procs = &device->vk_procs;
-
-    /* Check the actual semaphore value in case fence->value update is lagging. */
-    if ((vr = VK_CALL(vkGetSemaphoreCounterValueKHR(device->vk_device, fence->timeline_semaphore, &value))) < 0)
-    {
-        ERR("Failed to get Vulkan semaphore status, vr %d.\n", vr);
-        return false;
-    }
-
-    return value < fence->pending_timeline_value;
-}
-
-/* Replace the VkSemaphore with a new one to allow a lower value to be set. Ideally apps will
- * only use this to reset the fence when no operations are pending on the queue. */
-static HRESULT d3d12_fence_reinit_timeline_semaphore_locked(struct d3d12_fence *fence, uint64_t value)
-{
-    const struct d3d12_device *device = fence->device;
-    const struct vkd3d_vk_device_procs *vk_procs;
-    VkSemaphore timeline_semaphore;
-    VkResult vr;
-
-    if (d3d12_fence_has_pending_gpu_ops_locked(fence))
-    {
-        /* This situation is not very likely because it means a fence with pending waits and/or signals was
-         * signalled on the CPU to a lower value. For now, emit a fixme so it can be patched if necessary.
-         * A patch already exists for this but it's not pretty. */
-        FIXME("Unable to re-initialise timeline semaphore to a lower value due to pending GPU ops.\n");
-        return E_FAIL;
-    }
-
-    if ((vr = vkd3d_create_timeline_semaphore(device, value, &timeline_semaphore)) < 0)
-    {
-        WARN("Failed to create timeline semaphore, vr %d.\n", vr);
-        return hresult_from_vk_result(vr);
-    }
-
-    fence->value = value;
-    fence->pending_timeline_value = value;
-
-    WARN("Replacing timeline semaphore with a new object.\n");
-
-    vk_procs = &device->vk_procs;
-
-    VK_CALL(vkDestroySemaphore(device->vk_device, fence->timeline_semaphore, NULL));
-    fence->timeline_semaphore = timeline_semaphore;
-
-    return S_OK;
-}
-
 static HRESULT d3d12_fence_signal_cpu_timeline_semaphore(struct d3d12_fence *fence, uint64_t value)
 {
-    const struct d3d12_device *device = fence->device;
-    VkSemaphoreSignalInfoKHR info;
-    HRESULT hr = S_OK;
-    VkResult vr;
     int rc;
 
     if ((rc = vkd3d_mutex_lock(&fence->mutex)))
@@ -1162,48 +1231,13 @@ static HRESULT d3d12_fence_signal_cpu_timeline_semaphore(struct d3d12_fence *fen
         return hresult_from_errno(rc);
     }
 
-    /* We must only signal a value which is greater than the current value.
-     * That value can be in the range of current known value (fence->value), or as large as pending_timeline_value.
-     * Pending timeline value signal might be blocked by another synchronization primitive, and thus statically
-     * cannot be that value, so the safest thing to do is to check the current value which is updated by the fence
-     * wait thread continuously. This check is technically racy since the value might be immediately out of date,
-     * but there is no way to avoid this. */
-    if (value > fence->value)
-    {
-        const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
-
-        /* Sanity check against the delta limit. */
-        if (value - fence->value > device->vk_info.timeline_semaphore_properties.maxTimelineSemaphoreValueDifference)
-        {
-            FIXME("Timeline semaphore delta is %"PRIu64", but implementation only supports a delta of %"PRIu64".\n",
-                    value - fence->value, device->vk_info.timeline_semaphore_properties.maxTimelineSemaphoreValueDifference);
-        }
-
-        info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO_KHR;
-        info.pNext = NULL;
-        info.semaphore = fence->timeline_semaphore;
-        info.value = value;
-        if ((vr = VK_CALL(vkSignalSemaphoreKHR(device->vk_device, &info))) >= 0)
-        {
-            fence->value = value;
-            if (value > fence->pending_timeline_value)
-                fence->pending_timeline_value = value;
-        }
-        else
-        {
-            ERR("Failed to signal timeline semaphore, vr %d.\n", vr);
-            hr = hresult_from_vk_result(vr);
-        }
-    }
-    else if (value < fence->value)
-    {
-        hr = d3d12_fence_reinit_timeline_semaphore_locked(fence, value);
-    }
-
+    fence->value = value;
     d3d12_fence_signal_external_events_locked(fence);
+    d3d12_fence_update_pending_value_locked(fence);
 
     vkd3d_mutex_unlock(&fence->mutex);
-    return hr;
+
+    return d3d12_device_flush_blocked_queues(fence->device);
 }
 
 static HRESULT STDMETHODCALLTYPE d3d12_fence_Signal(ID3D12Fence *iface, UINT64 value)
@@ -1214,7 +1248,7 @@ static HRESULT STDMETHODCALLTYPE d3d12_fence_Signal(ID3D12Fence *iface, UINT64 v
 
     if (fence->timeline_semaphore)
         return d3d12_fence_signal_cpu_timeline_semaphore(fence, value);
-    return d3d12_fence_signal(fence, value, VK_NULL_HANDLE);
+    return d3d12_fence_signal(fence, value, VK_NULL_HANDLE, true);
 }
 
 static const struct ID3D12FenceVtbl d3d12_fence_vtbl =
@@ -1257,6 +1291,7 @@ static HRESULT d3d12_fence_init(struct d3d12_fence *fence, struct d3d12_device *
     fence->refcount = 1;
 
     fence->value = initial_value;
+    fence->max_pending_value = initial_value;
 
     if ((rc = vkd3d_mutex_init(&fence->mutex)))
     {
@@ -1279,15 +1314,15 @@ static HRESULT d3d12_fence_init(struct d3d12_fence *fence, struct d3d12_device *
     fence->event_count = 0;
 
     fence->timeline_semaphore = VK_NULL_HANDLE;
-    if (device->use_timeline_semaphores && (vr = vkd3d_create_timeline_semaphore(device, initial_value,
+    fence->timeline_value = 0;
+    fence->pending_timeline_value = 0;
+    if (device->vk_info.KHR_timeline_semaphore && (vr = vkd3d_create_timeline_semaphore(device, 0,
             &fence->timeline_semaphore)) < 0)
     {
         WARN("Failed to create timeline semaphore, vr %d.\n", vr);
         hr = hresult_from_vk_result(vr);
         goto fail_destroy_null_cond;
     }
-    fence->pending_timeline_value = initial_value;
-    fence->gpu_wait_count = 0;
 
     fence->semaphores = NULL;
     fence->semaphores_size = 0;
@@ -6136,6 +6171,9 @@ static ULONG STDMETHODCALLTYPE d3d12_command_queue_Release(ID3D12CommandQueue *i
 
         vkd3d_fence_worker_stop(&command_queue->fence_worker, device);
 
+        vkd3d_mutex_destroy(&command_queue->op_mutex);
+        vkd3d_free(command_queue->ops);
+
         vkd3d_private_store_destroy(&command_queue->private_store);
 
         vkd3d_free(command_queue);
@@ -6205,6 +6243,14 @@ static HRESULT STDMETHODCALLTYPE d3d12_command_queue_GetDevice(ID3D12CommandQueu
     return d3d12_device_query_interface(command_queue->device, iid, device);
 }
 
+static struct vkd3d_cs_op_data *d3d12_command_queue_require_space_locked(struct d3d12_command_queue *queue)
+{
+    if (!vkd3d_array_reserve((void **)&queue->ops, &queue->ops_size, queue->ops_count + 1, sizeof(*queue->ops)))
+        return NULL;
+
+    return &queue->ops[queue->ops_count++];
+}
+
 static void STDMETHODCALLTYPE d3d12_command_queue_UpdateTileMappings(ID3D12CommandQueue *iface,
         ID3D12Resource *resource, UINT region_count,
         const D3D12_TILED_RESOURCE_COORDINATE *region_start_coordinates,
@@ -6236,22 +6282,50 @@ static void STDMETHODCALLTYPE d3d12_command_queue_CopyTileMappings(ID3D12Command
             src_region_start_coordinate, region_size, flags);
 }
 
+static void d3d12_command_queue_execute(struct d3d12_command_queue *command_queue,
+        VkCommandBuffer *buffers, unsigned int count)
+{
+    const struct vkd3d_vk_device_procs *vk_procs = &command_queue->device->vk_procs;
+    struct vkd3d_queue *vkd3d_queue = command_queue->vkd3d_queue;
+    VkSubmitInfo submit_desc;
+    VkQueue vk_queue;
+    VkResult vr;
+
+    memset(&submit_desc, 0, sizeof(submit_desc));
+
+    if (!(vk_queue = vkd3d_queue_acquire(vkd3d_queue)))
+    {
+        ERR("Failed to acquire queue %p.\n", vkd3d_queue);
+        return;
+    }
+
+    submit_desc.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submit_desc.commandBufferCount = count;
+    submit_desc.pCommandBuffers = buffers;
+
+    if ((vr = VK_CALL(vkQueueSubmit(vk_queue, 1, &submit_desc, VK_NULL_HANDLE))) < 0)
+        ERR("Failed to submit queue(s), vr %d.\n", vr);
+
+    vkd3d_queue_release(vkd3d_queue);
+
+    vkd3d_free(buffers);
+}
+
 static void STDMETHODCALLTYPE d3d12_command_queue_ExecuteCommandLists(ID3D12CommandQueue *iface,
         UINT command_list_count, ID3D12CommandList * const *command_lists)
 {
     struct d3d12_command_queue *command_queue = impl_from_ID3D12CommandQueue(iface);
-    const struct vkd3d_vk_device_procs *vk_procs;
     struct d3d12_command_list *cmd_list;
-    struct VkSubmitInfo submit_desc;
+    struct vkd3d_cs_op_data *op;
     VkCommandBuffer *buffers;
-    VkQueue vk_queue;
     unsigned int i;
-    VkResult vr;
+    int rc;
 
     TRACE("iface %p, command_list_count %u, command_lists %p.\n",
             iface, command_list_count, command_lists);
 
-    vk_procs = &command_queue->device->vk_procs;
+    if (!command_list_count)
+        return;
 
     if (!(buffers = vkd3d_calloc(command_list_count, sizeof(*buffers))))
     {
@@ -6274,29 +6348,30 @@ static void STDMETHODCALLTYPE d3d12_command_queue_ExecuteCommandLists(ID3D12Comm
         buffers[i] = cmd_list->vk_command_buffer;
     }
 
-    submit_desc.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
-    submit_desc.pNext = NULL;
-    submit_desc.waitSemaphoreCount = 0;
-    submit_desc.pWaitSemaphores = NULL;
-    submit_desc.pWaitDstStageMask = NULL;
-    submit_desc.commandBufferCount = command_list_count;
-    submit_desc.pCommandBuffers = buffers;
-    submit_desc.signalSemaphoreCount = 0;
-    submit_desc.pSignalSemaphores = NULL;
-
-    if (!(vk_queue = vkd3d_queue_acquire(command_queue->vkd3d_queue)))
+    if ((rc = vkd3d_mutex_lock(&command_queue->op_mutex)))
     {
-        ERR("Failed to acquire queue %p.\n", command_queue->vkd3d_queue);
-        vkd3d_free(buffers);
+        ERR("Failed to lock mutex, error %d.\n", rc);
         return;
     }
 
-    if ((vr = VK_CALL(vkQueueSubmit(vk_queue, 1, &submit_desc, VK_NULL_HANDLE))) < 0)
-        ERR("Failed to submit queue(s), vr %d.\n", vr);
+    if (!command_queue->ops_count)
+    {
+        d3d12_command_queue_execute(command_queue, buffers, command_list_count);
+        vkd3d_mutex_unlock(&command_queue->op_mutex);
+        return;
+    }
 
-    vkd3d_queue_release(command_queue->vkd3d_queue);
+    if (!(op = d3d12_command_queue_require_space_locked(command_queue)))
+    {
+        ERR("Failed to add op.\n");
+        return;
+    }
+    op->opcode = VKD3D_CS_OP_EXECUTE;
+    op->u.execute.buffers = buffers;
+    op->u.execute.buffer_count = command_list_count;
 
-    vkd3d_free(buffers);
+    vkd3d_mutex_unlock(&command_queue->op_mutex);
+    return;
 }
 
 static void STDMETHODCALLTYPE d3d12_command_queue_SetMarker(ID3D12CommandQueue *iface,
@@ -6318,38 +6393,6 @@ static void STDMETHODCALLTYPE d3d12_command_queue_EndEvent(ID3D12CommandQueue *i
     FIXME("iface %p stub!\n", iface);
 }
 
-static HRESULT d3d12_fence_update_gpu_signal_timeline_semaphore(struct d3d12_fence *fence, uint64_t value)
-{
-    const struct d3d12_device *device = fence->device;
-    int rc;
-
-    if ((rc = vkd3d_mutex_lock(&fence->mutex)))
-    {
-        ERR("Failed to lock mutex, error %d.\n", rc);
-        return hresult_from_errno(rc);
-    }
-
-    /* If we're attempting to async signal a fence with a value which is not strictly increasing the payload value,
-     * warn about this case. Do not treat this as an error since it works at least with RADV and Nvidia drivers and
-     * there's no workaround on the GPU side. */
-    if (value <= fence->pending_timeline_value)
-    {
-        WARN("Fence %p values are not strictly increasing. Pending values: old %"PRIu64", new %"PRIu64".\n",
-                fence, fence->pending_timeline_value, value);
-    }
-    /* Sanity check against the delta limit. Use the current fence value. */
-    else if (value - fence->value > device->vk_info.timeline_semaphore_properties.maxTimelineSemaphoreValueDifference)
-    {
-        FIXME("Timeline semaphore delta is %"PRIu64", but implementation only supports a delta of %"PRIu64".\n",
-                value - fence->value, device->vk_info.timeline_semaphore_properties.maxTimelineSemaphoreValueDifference);
-    }
-    fence->pending_timeline_value = value;
-
-    vkd3d_mutex_unlock(&fence->mutex);
-
-    return S_OK;
-}
-
 static HRESULT vkd3d_enqueue_timeline_semaphore(struct vkd3d_fence_worker *worker, VkSemaphore vk_semaphore,
         struct d3d12_fence *fence, uint64_t value, struct vkd3d_queue *queue)
 {
@@ -6389,31 +6432,68 @@ static HRESULT STDMETHODCALLTYPE d3d12_command_queue_Signal(ID3D12CommandQueue *
         ID3D12Fence *fence_iface, UINT64 value)
 {
     struct d3d12_command_queue *command_queue = impl_from_ID3D12CommandQueue(iface);
+    struct d3d12_fence *fence = unsafe_impl_from_ID3D12Fence(fence_iface);
+    struct vkd3d_cs_op_data *op;
+    HRESULT hr = S_OK;
+    int rc;
+
+    TRACE("iface %p, fence %p, value %#"PRIx64".\n", iface, fence_iface, value);
+
+    if ((rc = vkd3d_mutex_lock(&command_queue->op_mutex)))
+    {
+        ERR("Failed to lock mutex, error %d.\n", rc);
+        return hresult_from_errno(rc);
+    }
+
+    if (!command_queue->ops_count)
+    {
+        hr = d3d12_command_queue_signal(command_queue, fence, value);
+        goto done;
+    }
+
+    if (!(op = d3d12_command_queue_require_space_locked(command_queue)))
+    {
+        hr = E_OUTOFMEMORY;
+        goto done;
+    }
+    op->opcode = VKD3D_CS_OP_SIGNAL;
+    op->u.signal.fence = fence;
+    op->u.signal.value = value;
+
+    d3d12_fence_incref(fence);
+
+done:
+    vkd3d_mutex_unlock(&command_queue->op_mutex);
+    return hr;
+}
+
+static HRESULT d3d12_command_queue_signal(struct d3d12_command_queue *command_queue,
+        struct d3d12_fence *fence, uint64_t value)
+{
     VkTimelineSemaphoreSubmitInfoKHR timeline_submit_info;
     const struct vkd3d_vk_device_procs *vk_procs;
     VkSemaphore vk_semaphore = VK_NULL_HANDLE;
     VkFence vk_fence = VK_NULL_HANDLE;
     struct vkd3d_queue *vkd3d_queue;
     uint64_t sequence_number = 0;
+    uint64_t timeline_value = 0;
     struct d3d12_device *device;
-    struct d3d12_fence *fence;
     VkSubmitInfo submit_info;
     VkQueue vk_queue;
     VkResult vr;
     HRESULT hr;
 
-    TRACE("iface %p, fence %p, value %#"PRIx64".\n", iface, fence_iface, value);
-
     device = command_queue->device;
     vk_procs = &device->vk_procs;
     vkd3d_queue = command_queue->vkd3d_queue;
 
-    fence = unsafe_impl_from_ID3D12Fence(fence_iface);
-
-    if (device->use_timeline_semaphores)
+    if (device->vk_info.KHR_timeline_semaphore)
     {
-        if (FAILED(hr = d3d12_fence_update_gpu_signal_timeline_semaphore(fence, value)))
-            return hr;
+        if (!(timeline_value = d3d12_fence_add_pending_timeline_signal(fence, value, vkd3d_queue)))
+        {
+            ERR("Failed to add pending signal.\n");
+            return E_OUTOFMEMORY;
+        }
 
         vk_semaphore = fence->timeline_semaphore;
         assert(vk_semaphore);
@@ -6434,7 +6514,7 @@ static HRESULT STDMETHODCALLTYPE d3d12_command_queue_Signal(ID3D12CommandQueue *
         goto fail;
     }
 
-    if (!device->use_timeline_semaphores && (vr = vkd3d_queue_create_vk_semaphore_locked(vkd3d_queue,
+    if (!device->vk_info.KHR_timeline_semaphore && (vr = vkd3d_queue_create_vk_semaphore_locked(vkd3d_queue,
             device, &vk_semaphore)) < 0)
     {
         ERR("Failed to create Vulkan semaphore, vr %d.\n", vr);
@@ -6451,11 +6531,11 @@ static HRESULT STDMETHODCALLTYPE d3d12_command_queue_Signal(ID3D12CommandQueue *
     submit_info.signalSemaphoreCount = vk_semaphore ? 1 : 0;
     submit_info.pSignalSemaphores = &vk_semaphore;
 
-    if (device->use_timeline_semaphores)
+    if (device->vk_info.KHR_timeline_semaphore)
     {
         timeline_submit_info.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR;
         timeline_submit_info.pNext = NULL;
-        timeline_submit_info.pSignalSemaphoreValues = &value;
+        timeline_submit_info.pSignalSemaphoreValues = &timeline_value;
         timeline_submit_info.signalSemaphoreValueCount = submit_info.signalSemaphoreCount;
         timeline_submit_info.waitSemaphoreValueCount = 0;
         timeline_submit_info.pWaitSemaphoreValues = NULL;
@@ -6463,7 +6543,7 @@ static HRESULT STDMETHODCALLTYPE d3d12_command_queue_Signal(ID3D12CommandQueue *
     }
 
     vr = VK_CALL(vkQueueSubmit(vk_queue, 1, &submit_info, vk_fence));
-    if (!device->use_timeline_semaphores && vr >= 0)
+    if (!device->vk_info.KHR_timeline_semaphore && vr >= 0)
     {
         sequence_number = ++vkd3d_queue->submitted_sequence_number;
 
@@ -6480,13 +6560,22 @@ static HRESULT STDMETHODCALLTYPE d3d12_command_queue_Signal(ID3D12CommandQueue *
         goto fail_vkresult;
     }
 
-    if (device->use_timeline_semaphores)
+    if (device->vk_info.KHR_timeline_semaphore)
     {
+        if (FAILED(hr = d3d12_fence_update_pending_value(fence)))
+            return hr;
+
+        if (FAILED(hr = d3d12_device_flush_blocked_queues(device)))
+            return hr;
+
+        vk_semaphore = fence->timeline_semaphore;
+        assert(vk_semaphore);
+
         return vkd3d_enqueue_timeline_semaphore(&command_queue->fence_worker,
-                vk_semaphore, fence, value, vkd3d_queue);
+                vk_semaphore, fence, timeline_value, vkd3d_queue);
     }
 
-    if (vk_semaphore && SUCCEEDED(hr = d3d12_fence_add_vk_semaphore(fence, vk_semaphore, vk_fence, value)))
+    if (vk_semaphore && SUCCEEDED(hr = d3d12_fence_add_vk_semaphore(fence, vk_semaphore, vk_fence, value, vkd3d_queue)))
         vk_semaphore = VK_NULL_HANDLE;
 
     vr = VK_CALL(vkGetFenceStatus(device->vk_device, vk_fence));
@@ -6501,7 +6590,7 @@ static HRESULT STDMETHODCALLTYPE d3d12_command_queue_Signal(ID3D12CommandQueue *
     else if (vr == VK_SUCCESS)
     {
         TRACE("Already signaled %p, value %#"PRIx64".\n", fence, value);
-        hr = d3d12_fence_signal(fence, value, vk_fence);
+        hr = d3d12_fence_signal(fence, value, vk_fence, false);
         vk_fence = VK_NULL_HANDLE;
         vkd3d_queue_update_sequence_number(vkd3d_queue, sequence_number, device);
     }
@@ -6524,12 +6613,12 @@ fail_vkresult:
     hr = hresult_from_vk_result(vr);
 fail:
     VK_CALL(vkDestroyFence(device->vk_device, vk_fence, NULL));
-    if (!device->use_timeline_semaphores)
+    if (!device->vk_info.KHR_timeline_semaphore)
         VK_CALL(vkDestroySemaphore(device->vk_device, vk_semaphore, NULL));
     return hr;
 }
 
-static HRESULT d3d12_command_queue_wait_binary_semaphore(struct d3d12_command_queue *command_queue,
+static HRESULT d3d12_command_queue_wait_binary_semaphore_locked(struct d3d12_command_queue *command_queue,
         struct d3d12_fence *fence, uint64_t value)
 {
     static const VkPipelineStageFlagBits wait_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
@@ -6545,7 +6634,10 @@ static HRESULT d3d12_command_queue_wait_binary_semaphore(struct d3d12_command_qu
     vk_procs = &command_queue->device->vk_procs;
     queue = command_queue->vkd3d_queue;
 
-    semaphore = d3d12_fence_acquire_vk_semaphore(fence, value, &completed_value);
+    semaphore = d3d12_fence_acquire_vk_semaphore_locked(fence, value, &completed_value);
+
+    vkd3d_mutex_unlock(&fence->mutex);
+
     if (!semaphore && completed_value >= value)
     {
         /* We don't get a Vulkan semaphore if the fence was signaled on CPU. */
@@ -6568,7 +6660,7 @@ static HRESULT d3d12_command_queue_wait_binary_semaphore(struct d3d12_command_qu
         }
         else
         {
-            FIXME("Failed to acquire Vulkan semaphore for fence %p, value %#"PRIx64
+            WARN("Failed to acquire Vulkan semaphore for fence %p, value %#"PRIx64
                     ", completed value %#"PRIx64".\n", fence, value, completed_value);
         }
 
@@ -6579,7 +6671,7 @@ static HRESULT d3d12_command_queue_wait_binary_semaphore(struct d3d12_command_qu
     submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
     submit_info.pNext = NULL;
     submit_info.waitSemaphoreCount = 1;
-    submit_info.pWaitSemaphores = &semaphore->vk_semaphore;
+    submit_info.pWaitSemaphores = &semaphore->u.binary.vk_semaphore;
     submit_info.pWaitDstStageMask = &wait_stage_mask;
     submit_info.commandBufferCount = 0;
     submit_info.pCommandBuffers = NULL;
@@ -6597,7 +6689,7 @@ static HRESULT d3d12_command_queue_wait_binary_semaphore(struct d3d12_command_qu
 
     if ((vr = VK_CALL(vkQueueSubmit(vk_queue, 1, &submit_info, VK_NULL_HANDLE))) >= 0)
     {
-        queue->semaphores[queue->semaphore_count].vk_semaphore = semaphore->vk_semaphore;
+        queue->semaphores[queue->semaphore_count].vk_semaphore = semaphore->u.binary.vk_semaphore;
         queue->semaphores[queue->semaphore_count].sequence_number = queue->submitted_sequence_number + 1;
         ++queue->semaphore_count;
 
@@ -6622,48 +6714,7 @@ fail:
     return hr;
 }
 
-static inline void d3d12_fence_update_gpu_wait(struct d3d12_fence *fence, const struct vkd3d_queue *queue)
-{
-    unsigned int i;
-    bool found;
-    int rc;
-
-    if ((rc = vkd3d_mutex_lock(&fence->mutex)))
-    {
-        ERR("Failed to lock mutex, error %d.\n", rc);
-        return;
-    }
-
-    for (i = 0, found = false; i < fence->gpu_wait_count; ++i)
-    {
-        if (fence->gpu_waits[i].queue == queue)
-        {
-            fence->gpu_waits[i].pending_value = queue->pending_wait_completion_value;
-            found = true;
-        }
-        else if (d3d12_fence_gpu_wait_is_completed(fence, i) && i < --fence->gpu_wait_count)
-        {
-            fence->gpu_waits[i] = fence->gpu_waits[fence->gpu_wait_count];
-        }
-    }
-
-    if (!found)
-    {
-        if (fence->gpu_wait_count < ARRAY_SIZE(fence->gpu_waits))
-        {
-            fence->gpu_waits[fence->gpu_wait_count].queue = queue;
-            fence->gpu_waits[fence->gpu_wait_count++].pending_value = queue->pending_wait_completion_value;
-        }
-        else
-        {
-            FIXME("Unable to track GPU fence wait.\n");
-        }
-    }
-
-    vkd3d_mutex_unlock(&fence->mutex);
-}
-
-static HRESULT d3d12_command_queue_wait_timeline_semaphore(struct d3d12_command_queue *command_queue,
+static HRESULT d3d12_command_queue_wait_locked(struct d3d12_command_queue *command_queue,
         struct d3d12_fence *fence, uint64_t value)
 {
     static const VkPipelineStageFlagBits wait_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
@@ -6671,25 +6722,29 @@ static HRESULT d3d12_command_queue_wait_timeline_semaphore(struct d3d12_command_
     const struct vkd3d_vk_device_procs *vk_procs;
     struct vkd3d_queue *queue;
     VkSubmitInfo submit_info;
+    uint64_t wait_value;
     VkQueue vk_queue;
     VkResult vr;
 
     vk_procs = &command_queue->device->vk_procs;
     queue = command_queue->vkd3d_queue;
 
+    if (!command_queue->device->vk_info.KHR_timeline_semaphore)
+        return d3d12_command_queue_wait_binary_semaphore_locked(command_queue, fence, value);
+
+    wait_value = d3d12_fence_get_timeline_wait_value_locked(fence, value);
+
+    /* We can unlock the fence here. The queue semaphore will not be signalled to signal_value
+     * until we have submitted, so the semaphore cannot be destroyed before the call to vkQueueSubmit. */
+    vkd3d_mutex_unlock(&fence->mutex);
+
     assert(fence->timeline_semaphore);
     timeline_submit_info.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR;
     timeline_submit_info.pNext = NULL;
+    timeline_submit_info.waitSemaphoreValueCount = 1;
+    timeline_submit_info.pWaitSemaphoreValues = &wait_value;
     timeline_submit_info.signalSemaphoreValueCount = 0;
     timeline_submit_info.pSignalSemaphoreValues = NULL;
-    timeline_submit_info.waitSemaphoreValueCount = 1;
-    timeline_submit_info.pWaitSemaphoreValues = &value;
-
-    if (!(vk_queue = vkd3d_queue_acquire(queue)))
-    {
-        ERR("Failed to acquire queue %p.\n", queue);
-        return E_FAIL;
-    }
 
     submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
     submit_info.pNext = &timeline_submit_info;
@@ -6701,14 +6756,11 @@ static HRESULT d3d12_command_queue_wait_timeline_semaphore(struct d3d12_command_
     submit_info.signalSemaphoreCount = 0;
     submit_info.pSignalSemaphores = NULL;
 
-    ++queue->pending_wait_completion_value;
-
-    submit_info.signalSemaphoreCount = 1;
-    submit_info.pSignalSemaphores = &queue->wait_completion_semaphore;
-    timeline_submit_info.signalSemaphoreValueCount = 1;
-    timeline_submit_info.pSignalSemaphoreValues = &queue->pending_wait_completion_value;
-
-    d3d12_fence_update_gpu_wait(fence, queue);
+    if (!(vk_queue = vkd3d_queue_acquire(queue)))
+    {
+        ERR("Failed to acquire queue %p.\n", queue);
+        return E_FAIL;
+    }
 
     vr = VK_CALL(vkQueueSubmit(vk_queue, 1, &submit_info, VK_NULL_HANDLE));
 
@@ -6728,14 +6780,58 @@ static HRESULT STDMETHODCALLTYPE d3d12_command_queue_Wait(ID3D12CommandQueue *if
 {
     struct d3d12_command_queue *command_queue = impl_from_ID3D12CommandQueue(iface);
     struct d3d12_fence *fence = unsafe_impl_from_ID3D12Fence(fence_iface);
+    struct vkd3d_cs_op_data *op;
+    HRESULT hr = S_OK;
+    int rc;
 
     TRACE("iface %p, fence %p, value %#"PRIx64".\n", iface, fence_iface, value);
 
-    if (command_queue->device->use_timeline_semaphores)
-        return d3d12_command_queue_wait_timeline_semaphore(command_queue, fence, value);
+    if ((rc = vkd3d_mutex_lock(&command_queue->op_mutex)))
+    {
+        ERR("Failed to lock mutex, error %d.\n", rc);
+        return hresult_from_errno(rc);
+    }
+    if ((rc = vkd3d_mutex_lock(&fence->mutex)))
+    {
+        ERR("Failed to lock mutex, error %d.\n", rc);
+        hr = hresult_from_errno(rc);
+        goto done;
+    }
 
-    FIXME_ONCE("KHR_timeline_semaphore is not available or incompatible. Some wait commands may be unsupported.\n");
-    return d3d12_command_queue_wait_binary_semaphore(command_queue, fence, value);
+    if (!command_queue->ops_count && value <= fence->max_pending_value)
+    {
+        hr = d3d12_command_queue_wait_locked(command_queue, fence, value);
+        goto done;
+    }
+
+    vkd3d_mutex_unlock(&fence->mutex);
+
+    /* This is the critical part required to support out-of-order signal.
+     * Normally we would be able to submit waits and signals out of order, but
+     * we don't have virtualized queues in Vulkan, so we need to handle the case
+     * where multiple queues alias over the same physical queue, so effectively,
+     * we need to manage out-of-order submits ourselves. */
+
+    if (!command_queue->ops_count)
+        hr = d3d12_device_add_blocked_command_queues(command_queue->device, &command_queue, 1);
+
+    if (FAILED(hr))
+        goto done;
+
+    if (!(op = d3d12_command_queue_require_space_locked(command_queue)))
+    {
+        hr = E_OUTOFMEMORY;
+        goto done;
+    }
+    op->opcode = VKD3D_CS_OP_WAIT;
+    op->u.wait.fence = fence;
+    op->u.wait.value = value;
+
+    d3d12_fence_incref(fence);
+
+done:
+    vkd3d_mutex_unlock(&command_queue->op_mutex);
+    return hr;
 }
 
 static HRESULT STDMETHODCALLTYPE d3d12_command_queue_GetTimestampFrequency(ID3D12CommandQueue *iface,
@@ -6859,10 +6955,82 @@ static const struct ID3D12CommandQueueVtbl d3d12_command_queue_vtbl =
     d3d12_command_queue_GetDesc,
 };
 
+/* flushed_any is initialised by the caller. */
+static bool d3d12_command_queue_flush_ops(struct d3d12_command_queue *queue, bool *flushed_any)
+{
+    struct vkd3d_cs_op_data *op;
+    struct d3d12_fence *fence;
+    bool flushed_all = false;
+    unsigned int i;
+    int rc;
+
+    if (!queue->ops_count)
+        return true;
+
+    /* This function may be re-entered during a call below to d3d12_command_queue_signal().
+     * We return true because the first caller is responsible for re-adding this queue to
+     * the flush list if it ends up returning false. */
+    if (queue->is_flushing)
+        return true;
+
+    if ((rc = vkd3d_mutex_lock(&queue->op_mutex)))
+    {
+        ERR("Failed to lock mutex, error %d.\n", rc);
+        return true;
+    }
+
+    /* Currently only required for d3d12_command_queue_signal(), but set it here anyway. */
+    queue->is_flushing = true;
+
+    for (i = 0; i < queue->ops_count; ++i)
+    {
+        op = &queue->ops[i];
+        switch (op->opcode)
+        {
+            case VKD3D_CS_OP_WAIT:
+                fence = op->u.wait.fence;
+                if (op->u.wait.value > fence->max_pending_value)
+                {
+                    queue->ops_count -= i;
+                    memmove(queue->ops, op, queue->ops_count * sizeof(*op));
+                    goto done;
+                }
+                vkd3d_mutex_lock(&fence->mutex);
+                d3d12_command_queue_wait_locked(queue, fence, op->u.wait.value);
+                d3d12_fence_decref(fence);
+                break;
+
+            case VKD3D_CS_OP_SIGNAL:
+                d3d12_command_queue_signal(queue, op->u.signal.fence, op->u.signal.value);
+                d3d12_fence_decref(op->u.signal.fence);
+                break;
+
+            case VKD3D_CS_OP_EXECUTE:
+                d3d12_command_queue_execute(queue, op->u.execute.buffers, op->u.execute.buffer_count);
+                break;
+
+            default:
+                FIXME("Unhandled op type %u.\n", op->opcode);
+                break;
+        }
+        *flushed_any |= true;
+    }
+
+    queue->ops_count = 0;
+    flushed_all = true;
+
+done:
+    queue->is_flushing = false;
+
+    vkd3d_mutex_unlock(&queue->op_mutex);
+    return flushed_all;
+}
+
 static HRESULT d3d12_command_queue_init(struct d3d12_command_queue *queue,
         struct d3d12_device *device, const D3D12_COMMAND_QUEUE_DESC *desc)
 {
     HRESULT hr;
+    int rc;
 
     queue->ID3D12CommandQueue_iface.lpVtbl = &d3d12_command_queue_vtbl;
     queue->refcount = 1;
@@ -6877,6 +7045,11 @@ static HRESULT d3d12_command_queue_init(struct d3d12_command_queue *queue,
     queue->last_waited_fence = NULL;
     queue->last_waited_fence_value = 0;
 
+    queue->ops = NULL;
+    queue->ops_count = 0;
+    queue->ops_size = 0;
+    queue->is_flushing = false;
+
     if (desc->Priority == D3D12_COMMAND_QUEUE_PRIORITY_GLOBAL_REALTIME)
     {
         FIXME("Global realtime priority is not implemented.\n");
@@ -6891,15 +7064,24 @@ static HRESULT d3d12_command_queue_init(struct d3d12_command_queue *queue,
     if (FAILED(hr = vkd3d_private_store_init(&queue->private_store)))
         return hr;
 
-    if (FAILED(hr = vkd3d_fence_worker_start(&queue->fence_worker, queue->vkd3d_queue, device)))
+    if ((rc = vkd3d_mutex_init(&queue->op_mutex)) < 0)
     {
-        vkd3d_private_store_destroy(&queue->private_store);
-        return hr;
+        hr = hresult_from_errno(rc);
+        goto fail_destroy_private_store;
     }
 
+    if (FAILED(hr = vkd3d_fence_worker_start(&queue->fence_worker, queue->vkd3d_queue, device)))
+        goto fail_destroy_op_mutex;
+
     d3d12_device_add_ref(queue->device = device);
 
     return S_OK;
+
+fail_destroy_op_mutex:
+    vkd3d_mutex_destroy(&queue->op_mutex);
+fail_destroy_private_store:
+    vkd3d_private_store_destroy(&queue->private_store);
+    return hr;
 }
 
 HRESULT d3d12_command_queue_create(struct d3d12_device *device,
@@ -6934,8 +7116,12 @@ uint32_t vkd3d_get_vk_queue_family_index(ID3D12CommandQueue *queue)
 VkQueue vkd3d_acquire_vk_queue(ID3D12CommandQueue *queue)
 {
     struct d3d12_command_queue *d3d12_queue = impl_from_ID3D12CommandQueue(queue);
+    VkQueue vk_queue = vkd3d_queue_acquire(d3d12_queue->vkd3d_queue);
+
+    if (d3d12_queue->ops_count)
+        WARN("Acquired command queue %p with %zu remaining ops.\n", d3d12_queue, d3d12_queue->ops_count);
 
-    return vkd3d_queue_acquire(d3d12_queue->vkd3d_queue);
+    return vk_queue;
 }
 
 void vkd3d_release_vk_queue(ID3D12CommandQueue *queue)
diff --git a/libs/vkd3d/device.c b/libs/vkd3d/device.c
index 5f8108ec..eaedc444 100644
--- a/libs/vkd3d/device.c
+++ b/libs/vkd3d/device.c
@@ -747,7 +747,6 @@ struct vkd3d_physical_device_info
     VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT texel_buffer_alignment_properties;
     VkPhysicalDeviceTransformFeedbackPropertiesEXT xfb_properties;
     VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT vertex_divisor_properties;
-    VkPhysicalDeviceTimelineSemaphorePropertiesKHR timeline_semaphore_properties;
 
     VkPhysicalDeviceProperties2KHR properties2;
 
@@ -772,7 +771,6 @@ static void vkd3d_physical_device_info_init(struct vkd3d_physical_device_info *i
     VkPhysicalDeviceDescriptorIndexingPropertiesEXT *descriptor_indexing_properties;
     VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT *vertex_divisor_properties;
     VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT *buffer_alignment_properties;
-    VkPhysicalDeviceTimelineSemaphorePropertiesKHR *timeline_semaphore_properties;
     VkPhysicalDeviceDescriptorIndexingFeaturesEXT *descriptor_indexing_features;
     VkPhysicalDeviceRobustness2FeaturesEXT *robustness2_features;
     VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT *vertex_divisor_features;
@@ -799,7 +797,6 @@ static void vkd3d_physical_device_info_init(struct vkd3d_physical_device_info *i
     vertex_divisor_features = &info->vertex_divisor_features;
     vertex_divisor_properties = &info->vertex_divisor_properties;
     timeline_semaphore_features = &info->timeline_semaphore_features;
-    timeline_semaphore_properties = &info->timeline_semaphore_properties;
     xfb_features = &info->xfb_features;
     xfb_properties = &info->xfb_properties;
 
@@ -841,8 +838,6 @@ static void vkd3d_physical_device_info_init(struct vkd3d_physical_device_info *i
     vk_prepend_struct(&info->properties2, xfb_properties);
     vertex_divisor_properties->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT;
     vk_prepend_struct(&info->properties2, vertex_divisor_properties);
-    timeline_semaphore_properties->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_PROPERTIES_KHR;
-    vk_prepend_struct(&info->properties2, timeline_semaphore_properties);
 
     if (vulkan_info->KHR_get_physical_device_properties2)
         VK_CALL(vkGetPhysicalDeviceProperties2KHR(physical_device, &info->properties2));
@@ -1431,7 +1426,6 @@ static HRESULT vkd3d_init_device_caps(struct d3d12_device *device,
     vulkan_info->rasterization_stream = physical_device_info->xfb_properties.transformFeedbackRasterizationStreamSelect;
     vulkan_info->transform_feedback_queries = physical_device_info->xfb_properties.transformFeedbackQueries;
     vulkan_info->max_vertex_attrib_divisor = max(physical_device_info->vertex_divisor_properties.maxVertexAttribDivisor, 1);
-    vulkan_info->timeline_semaphore_properties = physical_device_info->timeline_semaphore_properties;
 
     device->feature_options.DoublePrecisionFloatShaderOps = features->shaderFloat64;
     device->feature_options.OutputMergerLogicOp = features->logicOp;
@@ -1908,75 +1902,6 @@ static bool d3d12_is_64k_msaa_supported(struct d3d12_device *device)
             && info.Alignment <= 0x10000;
 }
 
-/* A lower value can be signalled on a D3D12 fence. Vulkan timeline semaphores
- * do not support this, but test if it works anyway. */
-static bool d3d12_is_timeline_semaphore_supported(const struct d3d12_device *device)
-{
-    const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
-    VkTimelineSemaphoreSubmitInfoKHR timeline_submit_info;
-    VkSemaphore timeline_semaphore;
-    VkSubmitInfo submit_info;
-    bool result = false;
-    uint64_t value = 0;
-    VkQueue vk_queue;
-    VkResult vr;
-
-    if (!device->vk_info.KHR_timeline_semaphore)
-        return false;
-
-    if ((vr = vkd3d_create_timeline_semaphore(device, 1, &timeline_semaphore)) < 0)
-    {
-        WARN("Failed to create timeline semaphore, vr %d.\n", vr);
-        return false;
-    }
-
-    if (!(vk_queue = vkd3d_queue_acquire(device->direct_queue)))
-    {
-        ERR("Failed to acquire queue %p.\n", device->direct_queue);
-        VK_CALL(vkDestroySemaphore(device->vk_device, timeline_semaphore, NULL));
-        return false;
-    }
-
-    submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
-    submit_info.pNext = &timeline_submit_info;
-    submit_info.waitSemaphoreCount = 0;
-    submit_info.pWaitSemaphores = NULL;
-    submit_info.pWaitDstStageMask = NULL;
-    submit_info.commandBufferCount = 0;
-    submit_info.pCommandBuffers = NULL;
-    submit_info.signalSemaphoreCount = 1;
-    submit_info.pSignalSemaphores = &timeline_semaphore;
-
-    timeline_submit_info.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR;
-    timeline_submit_info.pNext = NULL;
-    timeline_submit_info.pSignalSemaphoreValues = &value;
-    timeline_submit_info.signalSemaphoreValueCount = 1;
-    timeline_submit_info.waitSemaphoreValueCount = 0;
-    timeline_submit_info.pWaitSemaphoreValues = NULL;
-
-    vr = VK_CALL(vkQueueSubmit(vk_queue, 1, &submit_info, VK_NULL_HANDLE));
-
-    if (vr >= 0)
-    {
-        if ((vr = VK_CALL(vkQueueWaitIdle(vk_queue))) < 0)
-            WARN("Failed to wait for queue, vr %d.\n", vr);
-
-        if ((vr = VK_CALL(vkGetSemaphoreCounterValueKHR(device->vk_device, timeline_semaphore, &value))) < 0)
-            ERR("Failed to get Vulkan semaphore status, vr %d.\n", vr);
-        else if (!(result = !value))
-            WARN("Disabling timeline semaphore use due to incompatible behaviour.\n");
-    }
-    else
-    {
-        WARN("Failed to submit signal operation, vr %d.\n", vr);
-    }
-
-    vkd3d_queue_release(device->direct_queue);
-    VK_CALL(vkDestroySemaphore(device->vk_device, timeline_semaphore, NULL));
-
-    return result;
-}
-
 static HRESULT vkd3d_create_vk_device(struct d3d12_device *device,
         const struct vkd3d_device_create_info *create_info)
 {
@@ -2075,10 +2000,6 @@ static HRESULT vkd3d_create_vk_device(struct d3d12_device *device,
     }
 
     device->feature_options4.MSAA64KBAlignedTextureSupported = d3d12_is_64k_msaa_supported(device);
-    device->use_timeline_semaphores = d3d12_is_timeline_semaphore_supported(device)
-            && vkd3d_queue_init_timeline_semaphore(device->direct_queue, device)
-            && vkd3d_queue_init_timeline_semaphore(device->compute_queue, device)
-            && vkd3d_queue_init_timeline_semaphore(device->copy_queue, device);
 
     TRACE("Created Vulkan device %p.\n", vk_device);
 
@@ -4362,6 +4283,8 @@ static HRESULT d3d12_device_init(struct d3d12_device *device,
     vkd3d_gpu_va_allocator_init(&device->gpu_va_allocator);
     vkd3d_time_domains_init(device);
 
+    device->blocked_queue_count = 0;
+
     for (i = 0; i < ARRAY_SIZE(device->desc_mutex); ++i)
         vkd3d_mutex_init(&device->desc_mutex[i]);
 
diff --git a/libs/vkd3d/vkd3d_private.h b/libs/vkd3d/vkd3d_private.h
index 4e03145d..f00181a2 100644
--- a/libs/vkd3d/vkd3d_private.h
+++ b/libs/vkd3d/vkd3d_private.h
@@ -59,7 +59,7 @@
 #define VKD3D_MAX_SHADER_EXTENSIONS       3u
 #define VKD3D_MAX_SHADER_STAGES           5u
 #define VKD3D_MAX_VK_SYNC_OBJECTS         4u
-#define VKD3D_MAX_FENCE_WAITING_QUEUES    4u
+#define VKD3D_MAX_DEVICE_BLOCKED_QUEUES  16u
 #define VKD3D_MAX_DESCRIPTOR_SETS        64u
 /* D3D12 binding tier 3 has a limit of 2048 samplers. */
 #define VKD3D_MAX_DESCRIPTOR_SET_SAMPLERS 2048u
@@ -152,8 +152,6 @@ struct vkd3d_vulkan_info
 
     VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT texel_buffer_alignment_properties;
 
-    VkPhysicalDeviceTimelineSemaphorePropertiesKHR timeline_semaphore_properties;
-
     unsigned int shader_extension_count;
     enum vkd3d_shader_spirv_extension shader_extensions[VKD3D_MAX_SHADER_EXTENSIONS];
 
@@ -502,15 +500,17 @@ HRESULT vkd3d_set_private_data_interface(struct vkd3d_private_store *store, cons
 struct vkd3d_signaled_semaphore
 {
     uint64_t value;
-    VkSemaphore vk_semaphore;
-    VkFence vk_fence;
-    bool is_acquired;
-};
-
-struct vkd3d_pending_fence_wait
-{
-    const struct vkd3d_queue *queue;
-    uint64_t pending_value;
+    union
+    {
+        struct
+        {
+            VkSemaphore vk_semaphore;
+            VkFence vk_fence;
+            bool is_acquired;
+        } binary;
+        uint64_t timeline_value;
+    } u;
+    const struct vkd3d_queue *signalling_queue;
 };
 
 /* ID3D12Fence */
@@ -521,6 +521,7 @@ struct d3d12_fence
     LONG refcount;
 
     uint64_t value;
+    uint64_t max_pending_value;
     struct vkd3d_mutex mutex;
     struct vkd3d_cond null_event_cond;
 
@@ -534,9 +535,8 @@ struct d3d12_fence
     size_t event_count;
 
     VkSemaphore timeline_semaphore;
+    uint64_t timeline_value;
     uint64_t pending_timeline_value;
-    struct vkd3d_pending_fence_wait gpu_waits[VKD3D_MAX_FENCE_WAITING_QUEUES];
-    unsigned int gpu_wait_count;
 
     struct vkd3d_signaled_semaphore *semaphores;
     size_t semaphores_size;
@@ -1294,9 +1294,6 @@ struct vkd3d_queue
     VkQueueFlags vk_queue_flags;
     uint32_t timestamp_bits;
 
-    VkSemaphore wait_completion_semaphore;
-    uint64_t pending_wait_completion_value;
-
     struct
     {
         VkSemaphore vk_semaphore;
@@ -1311,10 +1308,45 @@ struct vkd3d_queue
 VkQueue vkd3d_queue_acquire(struct vkd3d_queue *queue);
 HRESULT vkd3d_queue_create(struct d3d12_device *device, uint32_t family_index,
         const VkQueueFamilyProperties *properties, struct vkd3d_queue **queue);
-bool vkd3d_queue_init_timeline_semaphore(struct vkd3d_queue *queue, struct d3d12_device *device);
 void vkd3d_queue_destroy(struct vkd3d_queue *queue, struct d3d12_device *device);
 void vkd3d_queue_release(struct vkd3d_queue *queue);
 
+enum vkd3d_cs_op
+{
+    VKD3D_CS_OP_WAIT,
+    VKD3D_CS_OP_SIGNAL,
+    VKD3D_CS_OP_EXECUTE,
+};
+
+struct vkd3d_cs_wait
+{
+    struct d3d12_fence *fence;
+    uint64_t value;
+};
+
+struct vkd3d_cs_signal
+{
+    struct d3d12_fence *fence;
+    uint64_t value;
+};
+
+struct vkd3d_cs_execute
+{
+    VkCommandBuffer *buffers;
+    unsigned int buffer_count;
+};
+
+struct vkd3d_cs_op_data
+{
+    enum vkd3d_cs_op opcode;
+    union
+    {
+        struct vkd3d_cs_wait wait;
+        struct vkd3d_cs_signal signal;
+        struct vkd3d_cs_execute execute;
+    } u;
+};
+
 /* ID3D12CommandQueue */
 struct d3d12_command_queue
 {
@@ -1331,6 +1363,12 @@ struct d3d12_command_queue
 
     struct d3d12_device *device;
 
+    struct vkd3d_mutex op_mutex;
+    struct vkd3d_cs_op_data *ops;
+    size_t ops_count;
+    size_t ops_size;
+    bool is_flushing;
+
     struct vkd3d_private_store private_store;
 };
 
@@ -1452,6 +1490,9 @@ struct d3d12_device
     unsigned int queue_family_count;
     VkTimeDomainEXT vk_host_time_domain;
 
+    struct d3d12_command_queue *blocked_queues[VKD3D_MAX_DEVICE_BLOCKED_QUEUES];
+    unsigned int blocked_queue_count;
+
     struct vkd3d_instance *vkd3d_instance;
 
     IUnknown *parent;
@@ -1470,7 +1511,6 @@ struct d3d12_device
     VkDescriptorPoolSize vk_pool_sizes[VKD3D_DESCRIPTOR_POOL_COUNT];
     struct vkd3d_vk_descriptor_heap_layout vk_descriptor_heap_layouts[VKD3D_SET_INDEX_COUNT];
     bool use_vk_heaps;
-    bool use_timeline_semaphores;
 };
 
 HRESULT d3d12_device_create(struct vkd3d_instance *instance,
diff --git a/tests/d3d12.c b/tests/d3d12.c
index 015c3122..5f83a373 100644
--- a/tests/d3d12.c
+++ b/tests/d3d12.c
@@ -33224,9 +33224,7 @@ static void test_queue_wait(void)
     command_list = context.list;
     queue = context.queue;
 
-    /* 'queue2' must not map to the same command queue as 'queue', or Wait() before GPU signal will fail.
-     * Using a compute queue fixes this on most hardware, but it may still fail on low spec hardware. */
-    queue2 = create_command_queue(device, D3D12_COMMAND_LIST_TYPE_COMPUTE, D3D12_COMMAND_QUEUE_PRIORITY_NORMAL);
+    queue2 = create_command_queue(device, D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_QUEUE_PRIORITY_NORMAL);
 
     event = create_event();
     ok(event, "Failed to create event.\n");
-- 
2.35.1




More information about the wine-devel mailing list