/* * Copyright © 2021 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "vk_sync.h" #include #include #include "util/u_debug.h" #include "util/macros.h" #include "util/os_time.h" #include "vk_alloc.h" #include "vk_device.h" #include "vk_log.h" #include "vk_physical_device.h" #include "vk_sync_binary.h" #include "vk_sync_dummy.h" #include "vk_sync_timeline.h" static void vk_sync_type_validate(const struct vk_sync_type *type) { assert(type->init); assert(type->finish); assert(type->features & (VK_SYNC_FEATURE_BINARY | VK_SYNC_FEATURE_TIMELINE)); if (type->features & VK_SYNC_FEATURE_TIMELINE) { assert(type->features & VK_SYNC_FEATURE_GPU_WAIT); assert(type->features & VK_SYNC_FEATURE_CPU_WAIT); assert(type->features & VK_SYNC_FEATURE_CPU_SIGNAL); assert(type->features & (VK_SYNC_FEATURE_WAIT_BEFORE_SIGNAL | VK_SYNC_FEATURE_WAIT_PENDING)); assert(type->signal); assert(type->get_value); } if (!(type->features & VK_SYNC_FEATURE_BINARY)) { assert(!(type->features & (VK_SYNC_FEATURE_GPU_MULTI_WAIT | VK_SYNC_FEATURE_CPU_RESET))); assert(!type->import_sync_file); assert(!type->export_sync_file); } if (type->features & VK_SYNC_FEATURE_CPU_WAIT) { assert(type->wait || type->wait_many); } else { assert(!(type->features & (VK_SYNC_FEATURE_WAIT_ANY | VK_SYNC_FEATURE_WAIT_PENDING))); } if (type->features & VK_SYNC_FEATURE_GPU_MULTI_WAIT) assert(type->features & VK_SYNC_FEATURE_GPU_WAIT); if (type->features & VK_SYNC_FEATURE_CPU_RESET) assert(type->reset); if (type->features & VK_SYNC_FEATURE_CPU_SIGNAL) assert(type->signal); } VkResult vk_sync_init(struct vk_device *device, struct vk_sync *sync, const struct vk_sync_type *type, enum vk_sync_flags flags, uint64_t initial_value) { vk_sync_type_validate(type); if (flags & VK_SYNC_IS_TIMELINE) assert(type->features & VK_SYNC_FEATURE_TIMELINE); else assert(type->features & VK_SYNC_FEATURE_BINARY); assert(type->size >= sizeof(*sync)); memset(sync, 0, type->size); sync->type = type; sync->flags = flags; return type->init(device, sync, initial_value); } void vk_sync_finish(struct vk_device *device, struct vk_sync *sync) { sync->type->finish(device, sync); } VkResult vk_sync_create(struct vk_device *device, const struct vk_sync_type *type, enum vk_sync_flags flags, uint64_t initial_value, struct vk_sync **sync_out) { struct vk_sync *sync; sync = vk_alloc(&device->alloc, type->size, 8, VK_SYSTEM_ALLOCATION_SCOPE_DEVICE); if (sync == NULL) return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY); VkResult result = vk_sync_init(device, sync, type, flags, initial_value); if (result != VK_SUCCESS) { vk_free(&device->alloc, sync); return result; } *sync_out = sync; return VK_SUCCESS; } void vk_sync_destroy(struct vk_device *device, struct vk_sync *sync) { vk_sync_finish(device, sync); vk_free(&device->alloc, sync); } static void assert_signal_valid(struct vk_sync *sync, uint64_t value) { assert(sync->type->features & VK_SYNC_FEATURE_CPU_SIGNAL); if (sync->flags & VK_SYNC_IS_TIMELINE) assert(value > 0); else assert(value == 0); } VkResult vk_sync_signal(struct vk_device *device, struct vk_sync *sync, uint64_t value) { assert_signal_valid(sync, value); return sync->type->signal(device, sync, value); } static bool can_signal_many(struct vk_device *device, uint32_t signal_count, const struct vk_sync_signal *signals) { if (signals[0].sync->type->signal_many == NULL) return false; /* If we only have one sync type, there's no need to check everything */ if (device->physical->supported_sync_types[1] == NULL) { assert(signals[0].sync->type == device->physical->supported_sync_types[0]); return true; } for (uint32_t i = 1; i < signal_count; i++) { if (signals[i].sync->type != signals[0].sync->type) return false; } return true; } VkResult vk_sync_signal_many(struct vk_device *device, uint32_t signal_count, const struct vk_sync_signal *signals) { if (signal_count == 0) return VK_SUCCESS; for (uint32_t i = 0; i < signal_count; i++) assert_signal_valid(signals[i].sync, signals[i].signal_value); if (can_signal_many(device, signal_count, signals)) return signals[0].sync->type->signal_many(device, signal_count, signals); for (uint32_t i = 0; i < signal_count; i++) { struct vk_sync *sync = signals[i].sync; uint64_t value = signals[i].signal_value; VkResult result = sync->type->signal(device, sync, value); if (unlikely(result != VK_SUCCESS)) return result; } return VK_SUCCESS; } VkResult vk_sync_get_value(struct vk_device *device, struct vk_sync *sync, uint64_t *value) { assert(sync->flags & VK_SYNC_IS_TIMELINE); return sync->type->get_value(device, sync, value); } static void assert_reset_valid(struct vk_sync *sync) { assert(sync->type->features & VK_SYNC_FEATURE_CPU_RESET); assert(!(sync->flags & VK_SYNC_IS_TIMELINE)); } VkResult vk_sync_reset(struct vk_device *device, struct vk_sync *sync) { assert_reset_valid(sync); return sync->type->reset(device, sync); } static bool can_reset_many(struct vk_device *device, uint32_t sync_count, struct vk_sync *const *syncs) { if (syncs[0]->type->reset_many == NULL) return false; if (device->physical->supported_sync_types[1] == NULL) { assert(syncs[0]->type == device->physical->supported_sync_types[0]); return true; } for (uint32_t i = 1; i < sync_count; i++) { if (syncs[i]->type != syncs[0]->type) return false; } return true; } VkResult vk_sync_reset_many(struct vk_device *device, uint32_t sync_count, struct vk_sync *const *syncs) { if (sync_count == 0) return VK_SUCCESS; for (uint32_t i = 0; i < sync_count; i++) assert_reset_valid(syncs[i]); if (can_reset_many(device, sync_count, syncs)) return syncs[0]->type->reset_many(device, sync_count, syncs); for (uint32_t i = 0; i < sync_count; i++) { VkResult result = syncs[i]->type->reset(device, syncs[i]); if (unlikely(result != VK_SUCCESS)) return result; } return VK_SUCCESS; } VkResult vk_sync_move(struct vk_device *device, struct vk_sync *dst, struct vk_sync *src) { assert(!(dst->flags & VK_SYNC_IS_TIMELINE)); assert(!(src->flags & VK_SYNC_IS_TIMELINE)); assert(dst->type == src->type); return src->type->move(device, dst, src); } static void assert_valid_wait(struct vk_sync *sync, uint64_t wait_value, enum vk_sync_wait_flags wait_flags) { assert(sync->type->features & VK_SYNC_FEATURE_CPU_WAIT); if (!(sync->flags & VK_SYNC_IS_TIMELINE)) assert(wait_value == 0); if (wait_flags & VK_SYNC_WAIT_PENDING) assert(sync->type->features & VK_SYNC_FEATURE_WAIT_PENDING); } static uint64_t get_max_abs_timeout_ns(void) { static int max_timeout_ms = -1; if (max_timeout_ms < 0) max_timeout_ms = debug_get_num_option("MESA_VK_MAX_TIMEOUT", 0); if (max_timeout_ms == 0) return UINT64_MAX; else return os_time_get_absolute_timeout(max_timeout_ms * 1000000ull); } static VkResult __vk_sync_wait(struct vk_device *device, struct vk_sync *sync, uint64_t wait_value, enum vk_sync_wait_flags wait_flags, uint64_t abs_timeout_ns) { assert_valid_wait(sync, wait_value, wait_flags); /* This doesn't make sense for a single wait */ assert(!(wait_flags & VK_SYNC_WAIT_ANY)); if (sync->type->wait) { return sync->type->wait(device, sync, wait_value, wait_flags, abs_timeout_ns); } else { struct vk_sync_wait wait = { .sync = sync, .stage_mask = ~(VkPipelineStageFlags2)0, .wait_value = wait_value, }; return sync->type->wait_many(device, 1, &wait, wait_flags, abs_timeout_ns); } } VkResult vk_sync_wait(struct vk_device *device, struct vk_sync *sync, uint64_t wait_value, enum vk_sync_wait_flags wait_flags, uint64_t abs_timeout_ns) { uint64_t max_abs_timeout_ns = get_max_abs_timeout_ns(); if (abs_timeout_ns > max_abs_timeout_ns) { VkResult result = __vk_sync_wait(device, sync, wait_value, wait_flags, max_abs_timeout_ns); if (unlikely(result == VK_TIMEOUT)) return vk_device_set_lost(device, "Maximum timeout exceeded!"); return result; } else { return __vk_sync_wait(device, sync, wait_value, wait_flags, abs_timeout_ns); } } static bool can_wait_many(struct vk_device *device, uint32_t wait_count, const struct vk_sync_wait *waits, enum vk_sync_wait_flags wait_flags) { if (waits[0].sync->type->wait_many == NULL) return false; if ((wait_flags & VK_SYNC_WAIT_ANY) && !(waits[0].sync->type->features & VK_SYNC_FEATURE_WAIT_ANY)) return false; /* If we only have one sync type, there's no need to check everything */ if (device->physical->supported_sync_types[1] == NULL) { assert(waits[0].sync->type == device->physical->supported_sync_types[0]); return true; } for (uint32_t i = 0; i < wait_count; i++) { assert_valid_wait(waits[i].sync, waits[i].wait_value, wait_flags); if (waits[i].sync->type != waits[0].sync->type) return false; } return true; } static VkResult __vk_sync_wait_many(struct vk_device *device, uint32_t wait_count, const struct vk_sync_wait *waits, enum vk_sync_wait_flags wait_flags, uint64_t abs_timeout_ns) { if (wait_count == 0) return VK_SUCCESS; if (wait_count == 1) { return __vk_sync_wait(device, waits[0].sync, waits[0].wait_value, wait_flags & ~VK_SYNC_WAIT_ANY, abs_timeout_ns); } if (can_wait_many(device, wait_count, waits, wait_flags)) { return waits[0].sync->type->wait_many(device, wait_count, waits, wait_flags, abs_timeout_ns); } else if (wait_flags & VK_SYNC_WAIT_ANY) { /* If we have multiple syncs and they don't support wait_any or they're * not all the same type, there's nothing better we can do than spin. */ do { for (uint32_t i = 0; i < wait_count; i++) { VkResult result = __vk_sync_wait(device, waits[i].sync, waits[i].wait_value, wait_flags & ~VK_SYNC_WAIT_ANY, 0 /* abs_timeout_ns */); if (result != VK_TIMEOUT) return result; } } while (os_time_get_nano() < abs_timeout_ns); return VK_TIMEOUT; } else { for (uint32_t i = 0; i < wait_count; i++) { VkResult result = __vk_sync_wait(device, waits[i].sync, waits[i].wait_value, wait_flags, abs_timeout_ns); if (result != VK_SUCCESS) return result; } return VK_SUCCESS; } } VkResult vk_sync_wait_many(struct vk_device *device, uint32_t wait_count, const struct vk_sync_wait *waits, enum vk_sync_wait_flags wait_flags, uint64_t abs_timeout_ns) { uint64_t max_abs_timeout_ns = get_max_abs_timeout_ns(); if (abs_timeout_ns > max_abs_timeout_ns) { VkResult result = __vk_sync_wait_many(device, wait_count, waits, wait_flags, max_abs_timeout_ns); if (unlikely(result == VK_TIMEOUT)) return vk_device_set_lost(device, "Maximum timeout exceeded!"); return result; } else { return __vk_sync_wait_many(device, wait_count, waits, wait_flags, abs_timeout_ns); } } VkResult vk_sync_import_opaque_fd(struct vk_device *device, struct vk_sync *sync, int fd) { VkResult result = sync->type->import_opaque_fd(device, sync, fd); if (unlikely(result != VK_SUCCESS)) return result; sync->flags |= VK_SYNC_IS_SHAREABLE | VK_SYNC_IS_SHARED; return VK_SUCCESS; } VkResult vk_sync_export_opaque_fd(struct vk_device *device, struct vk_sync *sync, int *fd) { assert(sync->flags & VK_SYNC_IS_SHAREABLE); VkResult result = sync->type->export_opaque_fd(device, sync, fd); if (unlikely(result != VK_SUCCESS)) return result; sync->flags |= VK_SYNC_IS_SHARED; return VK_SUCCESS; } VkResult vk_sync_import_sync_file(struct vk_device *device, struct vk_sync *sync, int sync_file) { assert(!(sync->flags & VK_SYNC_IS_TIMELINE)); /* Silently handle negative file descriptors in case the driver doesn't * want to bother. */ if (sync_file < 0 && sync->type->signal) return sync->type->signal(device, sync, 0); return sync->type->import_sync_file(device, sync, sync_file); } VkResult vk_sync_export_sync_file(struct vk_device *device, struct vk_sync *sync, int *sync_file) { assert(!(sync->flags & VK_SYNC_IS_TIMELINE)); return sync->type->export_sync_file(device, sync, sync_file); } VkResult vk_sync_import_win32_handle(struct vk_device *device, struct vk_sync *sync, void *handle, const wchar_t *name) { VkResult result = sync->type->import_win32_handle(device, sync, handle, name); if (unlikely(result != VK_SUCCESS)) return result; sync->flags |= VK_SYNC_IS_SHAREABLE | VK_SYNC_IS_SHARED; return VK_SUCCESS; } VkResult vk_sync_export_win32_handle(struct vk_device *device, struct vk_sync *sync, void **handle) { assert(sync->flags & VK_SYNC_IS_SHAREABLE); VkResult result = sync->type->export_win32_handle(device, sync, handle); if (unlikely(result != VK_SUCCESS)) return result; sync->flags |= VK_SYNC_IS_SHARED; return VK_SUCCESS; } VkResult vk_sync_set_win32_export_params(struct vk_device *device, struct vk_sync *sync, const void *security_attributes, uint32_t access, const wchar_t *name) { assert(sync->flags & VK_SYNC_IS_SHARED); return sync->type->set_win32_export_params(device, sync, security_attributes, access, name); } /** * Unwraps a vk_sync_wait, removing any vk_sync_timeline or vk_sync_binary * and replacing the sync with the actual driver primitive. * * After this is returns, wait->sync may be NULL, indicating no actual wait * is needed and this wait can be discarded. * * If the sync is a timeline, the point will be returned in point_out. * Otherwise point_out will be set to NULL. */ VkResult vk_sync_wait_unwrap(struct vk_device *device, struct vk_sync_wait *wait, struct vk_sync_timeline_point **point_out) { *point_out = NULL; if (wait->sync->flags & VK_SYNC_IS_TIMELINE) { if (wait->wait_value == 0) { *wait = (struct vk_sync_wait) { .sync = NULL }; return VK_SUCCESS; } } else { assert(wait->wait_value == 0); } struct vk_sync_timeline *timeline = vk_sync_as_timeline(wait->sync); if (timeline) { assert(device->timeline_mode == VK_DEVICE_TIMELINE_MODE_EMULATED); VkResult result = vk_sync_timeline_get_point(device, timeline, wait->wait_value, point_out); if (unlikely(result != VK_SUCCESS)) { /* vk_sync_timeline_get_point() returns VK_NOT_READY if no time * point can be found. Turn that into an actual error. */ return vk_errorf(device, VK_ERROR_UNKNOWN, "Time point >= %"PRIu64" not found", wait->wait_value); } /* This can happen if the point is long past */ if (*point_out == NULL) { *wait = (struct vk_sync_wait) { .sync = NULL }; return VK_SUCCESS; } wait->sync = &(*point_out)->sync; wait->wait_value = 0; } struct vk_sync_binary *binary = vk_sync_as_binary(wait->sync); if (binary) { wait->sync = &binary->timeline; wait->wait_value = binary->next_point; } if (vk_sync_type_is_dummy(wait->sync->type)) { if (*point_out != NULL) { vk_sync_timeline_point_unref(device, *point_out); *point_out = NULL; } *wait = (struct vk_sync_wait) { .sync = NULL }; return VK_SUCCESS; } return VK_SUCCESS; } /** * Unwraps a vk_sync_signal, removing any vk_sync_timeline or vk_sync_binary * and replacing the sync with the actual driver primitive. * * If the sync is a timeline, the point will be returned in point_out. This * point must be installed in the timeline after the actual signal has been * submitted to the kernel driver. Otherwise point_out will be set to NULL. */ VkResult vk_sync_signal_unwrap(struct vk_device *device, struct vk_sync_signal *signal, struct vk_sync_timeline_point **point_out) { if (signal->sync->flags & VK_SYNC_IS_TIMELINE) assert(signal->signal_value > 0); else assert(signal->signal_value == 0); *point_out = NULL; struct vk_sync_timeline *timeline = vk_sync_as_timeline(signal->sync); if (timeline) { assert(device->timeline_mode == VK_DEVICE_TIMELINE_MODE_EMULATED); VkResult result = vk_sync_timeline_alloc_point(device, timeline, signal->signal_value, point_out); if (unlikely(result != VK_SUCCESS)) return result; signal->sync = &(*point_out)->sync; signal->signal_value = 0; } struct vk_sync_binary *binary = vk_sync_as_binary(signal->sync); if (binary) { signal->sync = &binary->timeline; signal->signal_value = ++binary->next_point; } assert(!vk_sync_type_is_dummy(signal->sync->type)); return VK_SUCCESS; }