vulkan: Add a vk_device_copy_semaphore_payloads() helper

This acts as a vkQueueSubmit() except that it doesn't take any command
buffers or sparse binds and it doesn't act on a queue.  Instead, it just
copies semaphore payloads around using a new copy_sync_payloads vfunc on
vk_device.

Reviewed-by: Lars-Ivar Hesselberg Simonsen <lars-ivar.simonsen@arm.com>
Reviewed-by: Adam Jackson <ajax@redhat.com>
Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36827>
This commit is contained in:
Faith Ekstrand 2025-08-15 11:24:34 -04:00 committed by Marge Bot
parent 379cffc3bb
commit f4d00c1dbf
2 changed files with 201 additions and 0 deletions

View file

@ -25,10 +25,12 @@
#include "vk_alloc.h"
#include "vk_common_entrypoints.h"
#include "vk_fence.h"
#include "vk_instance.h"
#include "vk_log.h"
#include "vk_physical_device.h"
#include "vk_queue.h"
#include "vk_semaphore.h"
#include "vk_sync.h"
#include "vk_sync_timeline.h"
#include "vk_util.h"
@ -628,6 +630,170 @@ vk_common_DeviceWaitIdle(VkDevice _device)
return VK_SUCCESS;
}
VkResult
vk_device_copy_semaphore_payloads(struct vk_device *device,
uint32_t wait_semaphore_count,
const VkSemaphoreSubmitInfo *wait_semaphores,
uint32_t signal_semaphore_count,
const VkSemaphoreSubmitInfo *signal_semaphores,
uint32_t fence_count,
const VkFence *fences)
{
if (device->copy_sync_payloads == NULL)
return VK_ERROR_FEATURE_NOT_PRESENT;
STACK_ARRAY(struct vk_sync_wait, waits, wait_semaphore_count);
STACK_ARRAY(struct vk_sync_timeline_point *, wait_points,
wait_semaphore_count);
STACK_ARRAY(struct vk_sync *, resets, wait_semaphore_count);
STACK_ARRAY(struct vk_sync_signal, signals,
signal_semaphore_count + fence_count);
STACK_ARRAY(struct vk_sync_timeline_point *, signal_points,
signal_semaphore_count + fence_count);
uint32_t wait_count = 0, reset_count = 0, signal_count = 0;
VkResult result = VK_SUCCESS;
for (uint32_t i = 0; i < wait_semaphore_count; i++) {
VK_FROM_HANDLE(vk_semaphore, semaphore, wait_semaphores[i].semaphore);
struct vk_sync_wait wait = {
.sync = vk_semaphore_get_active_sync(semaphore),
.stage_mask = wait_semaphores[i].stageMask,
.wait_value = semaphore->type == VK_SEMAPHORE_TYPE_TIMELINE ?
wait_semaphores[i].value : 0,
};
struct vk_sync_timeline_point *wait_point = NULL;
VkResult result = vk_sync_wait_unwrap(device, &wait, &wait_point);
if (unlikely(result != VK_SUCCESS))
goto fail;
if (wait.sync == NULL)
continue;
wait_points[wait_count] = wait_point;
waits[wait_count] = wait;
wait_count++;
}
for (uint32_t i = 0; i < signal_semaphore_count; i++) {
VK_FROM_HANDLE(vk_semaphore, semaphore, signal_semaphores[i].semaphore);
if (semaphore->type == VK_SEMAPHORE_TYPE_TIMELINE &&
signal_semaphores[i].value == 0) {
result = vk_errorf(device, VK_ERROR_UNKNOWN,
"Tried to signal a timeline with value 0");
goto fail;
}
struct vk_sync_signal signal = {
.sync = vk_semaphore_get_active_sync(semaphore),
.stage_mask = signal_semaphores[i].stageMask,
.signal_value = semaphore->type == VK_SEMAPHORE_TYPE_TIMELINE ?
signal_semaphores[i].value : 0,
};
struct vk_sync_timeline_point *signal_point = NULL;
VkResult result = vk_sync_signal_unwrap(device, &signal, &signal_point);
if (unlikely(result != VK_SUCCESS))
goto fail;
signal_points[signal_count] = signal_point;
signals[signal_count] = signal;
signal_count++;
}
for (uint32_t i = 0; i < fence_count; i++) {
VK_FROM_HANDLE(vk_fence, fence, fences[i]);
struct vk_sync_signal signal = {
.sync = vk_fence_get_active_sync(fence),
.stage_mask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
};
struct vk_sync_timeline_point *signal_point = NULL;
VkResult result = vk_sync_signal_unwrap(device, &signal, &signal_point);
if (unlikely(result != VK_SUCCESS))
goto fail;
/* Timeline fences aren't a thing */
assert(signal_point == NULL);
signal_points[signal_count] = signal_point;
signals[signal_count] = signal;
signal_count++;
}
if (wait_count == 0) {
/* Nothing to wait on. Just signal everything */
result = vk_sync_signal_many(device, signal_count, signals);
if (result != VK_SUCCESS)
goto fail;
} else if (signal_count > 0) {
/* Wait for time points to materialize */
result = vk_sync_wait_many(device, wait_count, waits,
VK_SYNC_WAIT_PENDING, UINT64_MAX);
if (result != VK_SUCCESS)
goto fail;
/* Now do the copy */
result = device->copy_sync_payloads(device, wait_count, waits,
signal_count, signals);
if (result != VK_SUCCESS)
goto fail;
}
/* Reset any syncs which were waited on but not signaled */
for (uint32_t i = 0; i < wait_count; i++) {
bool was_signaled;
for (uint32_t j = 0; j < signal_count; j++) {
if (signals[j].sync == waits[i].sync) {
was_signaled = true;
break;
}
}
if (!was_signaled)
resets[reset_count++] = waits[i].sync;
}
if (reset_count > 0) {
result = vk_sync_reset_many(device, reset_count, resets);
if (result != VK_SUCCESS)
goto fail;
}
/* Reset any temporary semaphores we waited on */
for (uint32_t i = 0; i < wait_semaphore_count; i++) {
VK_FROM_HANDLE(vk_semaphore, semaphore, wait_semaphores[i].semaphore);
vk_semaphore_reset_temporary(device, semaphore);
}
/* Install time points */
for (uint32_t i = 0; i < signal_count; i++) {
if (signal_points[i] == NULL)
continue;
vk_sync_timeline_point_install(device, signal_points[i]);
/* Installing the point consumes our reference */
signal_points[i] = NULL;
}
fail:
for (uint32_t i = 0; i < signal_count; i++) {
if (signal_points[i] != NULL)
vk_sync_timeline_point_unref(device, signal_points[i]);
}
STACK_ARRAY_FINISH(waits);
STACK_ARRAY_FINISH(wait_points);
STACK_ARRAY_FINISH(resets);
STACK_ARRAY_FINISH(signals);
STACK_ARRAY_FINISH(signal_points);
return result;
}
VkResult
vk_device_get_timestamp(struct vk_device *device, VkTimeDomainKHR domain,
uint64_t *timestamp)

View file

@ -41,6 +41,8 @@ extern "C" {
struct vk_acceleration_structure_build_ops;
struct vk_command_buffer_ops;
struct vk_device_shader_ops;
struct vk_sync_signal;
struct vk_sync_wait;
enum vk_queue_submit_mode {
/** Submits happen immediately
@ -217,6 +219,25 @@ struct vk_device {
/** Period of VK_TIME_DOMAIN_DEVICE_KHR */
uint64_t device_time_domain_period;
/** Copies the sync payloads from the set of waits to the set of signals
*
* This effectively does the same as a vk_queue::driver_submit() with the
* given set of waits and signals and no command buffers, only without the
* queue. Instead, the driver is expected to simply copy the sync payloads
* from the wait set, merge them together into one, and apply that to the
* signals. After this function returns, all of the signals are now
* equivalent to all of the waits.
*
* This function MUST reset all binary syncs it waits on, unless that same
* sync is also used as a signal. Otherwise, we risk breaking timeline
* semaphore negotiation invariants.
*/
VkResult (*copy_sync_payloads)(struct vk_device *device,
uint32_t wait_count,
const struct vk_sync_wait *waits,
uint32_t signal_count,
const struct vk_sync_signal *signals);
/* Set by vk_device_set_drm_fd() */
struct util_sync_provider *sync;
@ -405,6 +426,20 @@ vk_device_check_status(struct vk_device *device)
return result;
}
/** Copy semaphore payloads to other semaphores/fences
*
* This is equivalent to doing VkQueueSubmit without any command buffers or
* sparse bind operations and without implicitly synchronizing on any queue.
*/
VkResult
vk_device_copy_semaphore_payloads(struct vk_device *device,
uint32_t wait_semaphore_count,
const VkSemaphoreSubmitInfo *wait_semaphores,
uint32_t signal_semaphore_count,
const VkSemaphoreSubmitInfo *signal_semaphores,
uint32_t fence_count,
const VkFence *fences);
VkResult
vk_device_get_timestamp(struct vk_device *device, VkTimeDomainKHR domain,
uint64_t *timestamp);