vulkan/queue: Rework vk_queue_submit()
Instead of basing everything on the timeline mode, base it on the submit mode of the queue. This makes a lot more sense since it's what we really care about anyway. Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15566>
This commit is contained in:
parent
e0ffdc8ce0
commit
8e51778acf
|
@ -255,6 +255,13 @@ vk_device_set_drm_fd(struct vk_device *device, int drm_fd)
|
||||||
void
|
void
|
||||||
vk_device_finish(struct vk_device *device);
|
vk_device_finish(struct vk_device *device);
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
vk_device_supports_threaded_submit(const struct vk_device *device)
|
||||||
|
{
|
||||||
|
return device->submit_mode == VK_QUEUE_SUBMIT_MODE_THREADED ||
|
||||||
|
device->submit_mode == VK_QUEUE_SUBMIT_MODE_THREADED_ON_DEMAND;
|
||||||
|
}
|
||||||
|
|
||||||
VkResult vk_device_flush(struct vk_device *device);
|
VkResult vk_device_flush(struct vk_device *device);
|
||||||
|
|
||||||
VkResult PRINTFLIKE(4, 5)
|
VkResult PRINTFLIKE(4, 5)
|
||||||
|
|
|
@ -646,8 +646,7 @@ vk_queue_submit(struct vk_queue *queue,
|
||||||
semaphore->temporary = NULL;
|
semaphore->temporary = NULL;
|
||||||
} else {
|
} else {
|
||||||
if (semaphore->type == VK_SEMAPHORE_TYPE_BINARY) {
|
if (semaphore->type == VK_SEMAPHORE_TYPE_BINARY) {
|
||||||
if (queue->base.device->timeline_mode ==
|
if (vk_device_supports_threaded_submit(device))
|
||||||
VK_DEVICE_TIMELINE_MODE_ASSISTED)
|
|
||||||
assert(semaphore->permanent.type->move);
|
assert(semaphore->permanent.type->move);
|
||||||
has_binary_permanent_semaphore_wait = true;
|
has_binary_permanent_semaphore_wait = true;
|
||||||
}
|
}
|
||||||
|
@ -810,164 +809,164 @@ vk_queue_submit(struct vk_queue *queue,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (queue->base.device->timeline_mode) {
|
switch (queue->submit.mode) {
|
||||||
case VK_DEVICE_TIMELINE_MODE_ASSISTED:
|
case VK_QUEUE_SUBMIT_MODE_IMMEDIATE:
|
||||||
if (queue->submit.mode == VK_QUEUE_SUBMIT_MODE_THREADED) {
|
result = vk_queue_submit_final(queue, submit);
|
||||||
if (has_binary_permanent_semaphore_wait) {
|
if (unlikely(result != VK_SUCCESS))
|
||||||
for (uint32_t i = 0; i < info->wait_count; i++) {
|
goto fail;
|
||||||
VK_FROM_HANDLE(vk_semaphore, semaphore,
|
|
||||||
info->waits[i].semaphore);
|
|
||||||
|
|
||||||
if (semaphore->type != VK_SEMAPHORE_TYPE_BINARY)
|
/* If threaded submit is possible on this device, we need to ensure that
|
||||||
continue;
|
* binary semaphore payloads get reset so that any other threads can
|
||||||
|
* properly wait on them for dependency checking. Because we don't
|
||||||
|
* currently have a submit thread, we can directly reset that binary
|
||||||
|
* semaphore payloads.
|
||||||
|
*
|
||||||
|
* If we the vk_sync is in our signal et, we can consider it to have
|
||||||
|
* been both reset and signaled by queue_submit_final(). A reset in
|
||||||
|
* this case would be wrong because it would throw away our signal
|
||||||
|
* operation. If we don't signal the vk_sync, then we need to reset it.
|
||||||
|
*/
|
||||||
|
if (vk_device_supports_threaded_submit(device) &&
|
||||||
|
has_binary_permanent_semaphore_wait) {
|
||||||
|
for (uint32_t i = 0; i < submit->wait_count; i++) {
|
||||||
|
if ((submit->waits[i].sync->flags & VK_SYNC_IS_TIMELINE) ||
|
||||||
|
submit->_wait_temps[i] != NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
/* From the Vulkan 1.2.194 spec:
|
bool was_signaled = false;
|
||||||
*
|
for (uint32_t j = 0; j < submit->signal_count; j++) {
|
||||||
* "When a batch is submitted to a queue via a queue
|
if (submit->signals[j].sync == submit->waits[i].sync) {
|
||||||
* submission, and it includes semaphores to be waited on,
|
was_signaled = true;
|
||||||
* it defines a memory dependency between prior semaphore
|
break;
|
||||||
* signal operations and the batch, and defines semaphore
|
|
||||||
* wait operations.
|
|
||||||
*
|
|
||||||
* Such semaphore wait operations set the semaphores
|
|
||||||
* created with a VkSemaphoreType of
|
|
||||||
* VK_SEMAPHORE_TYPE_BINARY to the unsignaled state."
|
|
||||||
*
|
|
||||||
* For threaded submit, we depend on tracking the unsignaled
|
|
||||||
* state of binary semaphores to determine when we can safely
|
|
||||||
* submit. The VK_SYNC_WAIT_PENDING check above as well as the
|
|
||||||
* one in the sumbit thread depend on all binary semaphores
|
|
||||||
* being reset when they're not in active use from the point
|
|
||||||
* of view of the client's CPU timeline. This means we need to
|
|
||||||
* reset them inside vkQueueSubmit and cannot wait until the
|
|
||||||
* actual submit which happens later in the thread.
|
|
||||||
*
|
|
||||||
* We've already stolen temporary semaphore payloads above as
|
|
||||||
* part of basic semaphore processing. We steal permanent
|
|
||||||
* semaphore payloads here by way of vk_sync_move. For shared
|
|
||||||
* semaphores, this can be a bit expensive (sync file import
|
|
||||||
* and export) but, for non-shared semaphores, it can be made
|
|
||||||
* fairly cheap. Also, we only do this semaphore swapping in
|
|
||||||
* the case where you have real timelines AND the client is
|
|
||||||
* using timeline semaphores with wait-before-signal (that's
|
|
||||||
* the only way to get a submit thread) AND mixing those with
|
|
||||||
* waits on binary semaphores AND said binary semaphore is
|
|
||||||
* using its permanent payload. In other words, this code
|
|
||||||
* should basically only ever get executed in CTS tests.
|
|
||||||
*/
|
|
||||||
if (submit->_wait_temps[i] != NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
assert(submit->waits[i].sync == &semaphore->permanent);
|
|
||||||
|
|
||||||
/* From the Vulkan 1.2.194 spec:
|
|
||||||
*
|
|
||||||
* VUID-vkQueueSubmit-pWaitSemaphores-03238
|
|
||||||
*
|
|
||||||
* "All elements of the pWaitSemaphores member of all
|
|
||||||
* elements of pSubmits created with a VkSemaphoreType of
|
|
||||||
* VK_SEMAPHORE_TYPE_BINARY must reference a semaphore
|
|
||||||
* signal operation that has been submitted for execution
|
|
||||||
* and any semaphore signal operations on which it depends
|
|
||||||
* (if any) must have also been submitted for execution."
|
|
||||||
*
|
|
||||||
* Therefore, we can safely do a blocking wait here and it
|
|
||||||
* won't actually block for long. This ensures that the
|
|
||||||
* vk_sync_move below will succeed.
|
|
||||||
*/
|
|
||||||
result = vk_sync_wait(queue->base.device,
|
|
||||||
submit->waits[i].sync, 0,
|
|
||||||
VK_SYNC_WAIT_PENDING, UINT64_MAX);
|
|
||||||
if (unlikely(result != VK_SUCCESS))
|
|
||||||
goto fail;
|
|
||||||
|
|
||||||
result = vk_sync_create(queue->base.device,
|
|
||||||
semaphore->permanent.type,
|
|
||||||
0 /* flags */,
|
|
||||||
0 /* initial value */,
|
|
||||||
&submit->_wait_temps[i]);
|
|
||||||
if (unlikely(result != VK_SUCCESS))
|
|
||||||
goto fail;
|
|
||||||
|
|
||||||
result = vk_sync_move(queue->base.device,
|
|
||||||
submit->_wait_temps[i],
|
|
||||||
&semaphore->permanent);
|
|
||||||
if (unlikely(result != VK_SUCCESS))
|
|
||||||
goto fail;
|
|
||||||
|
|
||||||
submit->waits[i].sync = submit->_wait_temps[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vk_queue_push_submit(queue, submit);
|
|
||||||
|
|
||||||
if (signal_mem_sync) {
|
|
||||||
/* If we're signaling a memory object, we have to ensure that
|
|
||||||
* vkQueueSubmit does not return until the kernel submission has
|
|
||||||
* happened. Otherwise, we may get a race between this process
|
|
||||||
* and whatever is going to wait on the object where the other
|
|
||||||
* process may wait before we've submitted our work. Drain the
|
|
||||||
* queue now to avoid this. It's the responsibility of the caller
|
|
||||||
* to ensure that any vkQueueSubmit which signals a memory object
|
|
||||||
* has fully resolved dependencies.
|
|
||||||
*/
|
|
||||||
result = vk_queue_drain(queue);
|
|
||||||
if (unlikely(result != VK_SUCCESS))
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return VK_SUCCESS;
|
|
||||||
} else {
|
|
||||||
result = vk_queue_submit_final(queue, submit);
|
|
||||||
if (unlikely(result != VK_SUCCESS))
|
|
||||||
goto fail;
|
|
||||||
|
|
||||||
/* If we don't have a submit thread, we can more directly ensure
|
|
||||||
* that binary semaphore payloads get reset. If we also signal the
|
|
||||||
* vk_sync, then we can consider it to have been both reset and
|
|
||||||
* signaled. A reset in this case would be wrong because it would
|
|
||||||
* throw away our signal operation. If we don't signal the vk_sync,
|
|
||||||
* then we need to reset it.
|
|
||||||
*/
|
|
||||||
if (has_binary_permanent_semaphore_wait) {
|
|
||||||
for (uint32_t i = 0; i < submit->wait_count; i++) {
|
|
||||||
if ((submit->waits[i].sync->flags & VK_SYNC_IS_TIMELINE) ||
|
|
||||||
submit->_wait_temps[i] != NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool was_signaled = false;
|
|
||||||
for (uint32_t j = 0; j < submit->signal_count; j++) {
|
|
||||||
if (submit->signals[j].sync == submit->waits[i].sync) {
|
|
||||||
was_signaled = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!was_signaled) {
|
|
||||||
result = vk_sync_reset(queue->base.device,
|
|
||||||
submit->waits[i].sync);
|
|
||||||
if (unlikely(result != VK_SUCCESS))
|
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
vk_queue_submit_destroy(queue, submit);
|
if (!was_signaled) {
|
||||||
return VK_SUCCESS;
|
result = vk_sync_reset(queue->base.device,
|
||||||
|
submit->waits[i].sync);
|
||||||
|
if (unlikely(result != VK_SUCCESS))
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
unreachable("Should have returned");
|
|
||||||
|
|
||||||
case VK_DEVICE_TIMELINE_MODE_EMULATED:
|
vk_queue_submit_destroy(queue, submit);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
case VK_QUEUE_SUBMIT_MODE_DEFERRED:
|
||||||
vk_queue_push_submit(queue, submit);
|
vk_queue_push_submit(queue, submit);
|
||||||
return vk_device_flush(queue->base.device);
|
return vk_device_flush(queue->base.device);
|
||||||
|
|
||||||
case VK_DEVICE_TIMELINE_MODE_NONE:
|
case VK_QUEUE_SUBMIT_MODE_THREADED:
|
||||||
case VK_DEVICE_TIMELINE_MODE_NATIVE:
|
if (has_binary_permanent_semaphore_wait) {
|
||||||
result = vk_queue_submit_final(queue, submit);
|
for (uint32_t i = 0; i < info->wait_count; i++) {
|
||||||
vk_queue_submit_destroy(queue, submit);
|
VK_FROM_HANDLE(vk_semaphore, semaphore,
|
||||||
return result;
|
info->waits[i].semaphore);
|
||||||
|
|
||||||
|
if (semaphore->type != VK_SEMAPHORE_TYPE_BINARY)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* From the Vulkan 1.2.194 spec:
|
||||||
|
*
|
||||||
|
* "When a batch is submitted to a queue via a queue
|
||||||
|
* submission, and it includes semaphores to be waited on,
|
||||||
|
* it defines a memory dependency between prior semaphore
|
||||||
|
* signal operations and the batch, and defines semaphore
|
||||||
|
* wait operations.
|
||||||
|
*
|
||||||
|
* Such semaphore wait operations set the semaphores
|
||||||
|
* created with a VkSemaphoreType of
|
||||||
|
* VK_SEMAPHORE_TYPE_BINARY to the unsignaled state."
|
||||||
|
*
|
||||||
|
* For threaded submit, we depend on tracking the unsignaled
|
||||||
|
* state of binary semaphores to determine when we can safely
|
||||||
|
* submit. The VK_SYNC_WAIT_PENDING check above as well as the
|
||||||
|
* one in the sumbit thread depend on all binary semaphores
|
||||||
|
* being reset when they're not in active use from the point
|
||||||
|
* of view of the client's CPU timeline. This means we need to
|
||||||
|
* reset them inside vkQueueSubmit and cannot wait until the
|
||||||
|
* actual submit which happens later in the thread.
|
||||||
|
*
|
||||||
|
* We've already stolen temporary semaphore payloads above as
|
||||||
|
* part of basic semaphore processing. We steal permanent
|
||||||
|
* semaphore payloads here by way of vk_sync_move. For shared
|
||||||
|
* semaphores, this can be a bit expensive (sync file import
|
||||||
|
* and export) but, for non-shared semaphores, it can be made
|
||||||
|
* fairly cheap. Also, we only do this semaphore swapping in
|
||||||
|
* the case where you have real timelines AND the client is
|
||||||
|
* using timeline semaphores with wait-before-signal (that's
|
||||||
|
* the only way to get a submit thread) AND mixing those with
|
||||||
|
* waits on binary semaphores AND said binary semaphore is
|
||||||
|
* using its permanent payload. In other words, this code
|
||||||
|
* should basically only ever get executed in CTS tests.
|
||||||
|
*/
|
||||||
|
if (submit->_wait_temps[i] != NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
assert(submit->waits[i].sync == &semaphore->permanent);
|
||||||
|
|
||||||
|
/* From the Vulkan 1.2.194 spec:
|
||||||
|
*
|
||||||
|
* VUID-vkQueueSubmit-pWaitSemaphores-03238
|
||||||
|
*
|
||||||
|
* "All elements of the pWaitSemaphores member of all
|
||||||
|
* elements of pSubmits created with a VkSemaphoreType of
|
||||||
|
* VK_SEMAPHORE_TYPE_BINARY must reference a semaphore
|
||||||
|
* signal operation that has been submitted for execution
|
||||||
|
* and any semaphore signal operations on which it depends
|
||||||
|
* (if any) must have also been submitted for execution."
|
||||||
|
*
|
||||||
|
* Therefore, we can safely do a blocking wait here and it
|
||||||
|
* won't actually block for long. This ensures that the
|
||||||
|
* vk_sync_move below will succeed.
|
||||||
|
*/
|
||||||
|
result = vk_sync_wait(queue->base.device,
|
||||||
|
submit->waits[i].sync, 0,
|
||||||
|
VK_SYNC_WAIT_PENDING, UINT64_MAX);
|
||||||
|
if (unlikely(result != VK_SUCCESS))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
result = vk_sync_create(queue->base.device,
|
||||||
|
semaphore->permanent.type,
|
||||||
|
0 /* flags */,
|
||||||
|
0 /* initial value */,
|
||||||
|
&submit->_wait_temps[i]);
|
||||||
|
if (unlikely(result != VK_SUCCESS))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
result = vk_sync_move(queue->base.device,
|
||||||
|
submit->_wait_temps[i],
|
||||||
|
&semaphore->permanent);
|
||||||
|
if (unlikely(result != VK_SUCCESS))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
submit->waits[i].sync = submit->_wait_temps[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk_queue_push_submit(queue, submit);
|
||||||
|
|
||||||
|
if (signal_mem_sync) {
|
||||||
|
/* If we're signaling a memory object, we have to ensure that
|
||||||
|
* vkQueueSubmit does not return until the kernel submission has
|
||||||
|
* happened. Otherwise, we may get a race between this process
|
||||||
|
* and whatever is going to wait on the object where the other
|
||||||
|
* process may wait before we've submitted our work. Drain the
|
||||||
|
* queue now to avoid this. It's the responsibility of the caller
|
||||||
|
* to ensure that any vkQueueSubmit which signals a memory object
|
||||||
|
* has fully resolved dependencies.
|
||||||
|
*/
|
||||||
|
result = vk_queue_drain(queue);
|
||||||
|
if (unlikely(result != VK_SUCCESS))
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return VK_SUCCESS;
|
||||||
|
|
||||||
|
case VK_QUEUE_SUBMIT_MODE_THREADED_ON_DEMAND:
|
||||||
|
unreachable("Invalid vk_queue::submit.mode");
|
||||||
}
|
}
|
||||||
unreachable("Invalid timeline mode");
|
unreachable("Invalid submit mode");
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
vk_queue_submit_destroy(queue, submit);
|
vk_queue_submit_destroy(queue, submit);
|
||||||
|
|
Loading…
Reference in New Issue