From f2f91cf570ad6736a62192f2cb36b6c2887a0fda Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Fri, 29 Sep 2017 00:36:46 +0200 Subject: vo_gpu: vulkan: add a vk_signal abstraction This combines VkSemaphores and VkEvents into a common umbrella abstraction which can resolve to either. We aggressively try to prefer VkEvents over VkSemaphores whenever the conditions are met (1. we can unsignal the semaphore, i.e. it comes from the same frame; and 2. it comes from the same queue). --- video/out/vulkan/common.h | 4 ++ video/out/vulkan/utils.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++ video/out/vulkan/utils.h | 29 ++++++++++++ 3 files changed, 144 insertions(+) (limited to 'video/out') diff --git a/video/out/vulkan/common.h b/video/out/vulkan/common.h index 35c5b3dbfb..de49c6f1af 100644 --- a/video/out/vulkan/common.h +++ b/video/out/vulkan/common.h @@ -53,6 +53,10 @@ struct mpvk_ctx { struct vk_cmd *last_cmd; // most recently submitted (pending) command struct spirv_compiler *spirv; // GLSL -> SPIR-V compiler + // Common pool of signals, to avoid having to re-create these objects often + struct vk_signal **signals; + int num_signals; + // Cached capabilities VkPhysicalDeviceLimits limits; }; diff --git a/video/out/vulkan/utils.c b/video/out/vulkan/utils.c index ee5a524947..9d9d8d9820 100644 --- a/video/out/vulkan/utils.c +++ b/video/out/vulkan/utils.c @@ -140,6 +140,9 @@ void mpvk_uninit(struct mpvk_ctx *vk) if (vk->dev) { vk_cmdpool_destroy(vk, vk->pool); + for (int i = 0; i < vk->num_signals; i++) + vk_signal_destroy(vk, &vk->signals[i]); + talloc_free(vk->signals); vk_malloc_uninit(vk); vkDestroyDevice(vk->dev, MPVK_ALLOCATOR); } @@ -726,6 +729,114 @@ error: return ret; } +void vk_signal_destroy(struct mpvk_ctx *vk, struct vk_signal **sig) +{ + if (!*sig) + return; + + vkDestroySemaphore(vk->dev, (*sig)->semaphore, MPVK_ALLOCATOR); + vkDestroyEvent(vk->dev, (*sig)->event, MPVK_ALLOCATOR); + talloc_free(*sig); + *sig = NULL; +} + +struct vk_signal *vk_cmd_signal(struct mpvk_ctx *vk, struct vk_cmd *cmd, + VkPipelineStageFlags stage) +{ + struct vk_signal *sig = NULL; + if (MP_TARRAY_POP(vk->signals, vk->num_signals, &sig)) + goto done; + + // no available signal => initialize a new one + sig = talloc_zero(NULL, struct vk_signal); + static const VkSemaphoreCreateInfo sinfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + + VK(vkCreateSemaphore(vk->dev, &sinfo, MPVK_ALLOCATOR, &sig->semaphore)); + + static const VkEventCreateInfo einfo = { + .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO, + }; + + VK(vkCreateEvent(vk->dev, &einfo, MPVK_ALLOCATOR, &sig->event)); + +done: + // Signal both the semaphore and the event. (We will only end up using one) + vk_cmd_sig(cmd, sig->semaphore); + vkCmdSetEvent(cmd->buf, sig->event, stage); + sig->event_source = cmd->queue; + return sig; + +error: + vk_signal_destroy(vk, &sig); + return NULL; +} + +static bool unsignal_cmd(struct vk_cmd *cmd, VkSemaphore sem) +{ + for (int n = 0; n < cmd->num_sigs; n++) { + if (cmd->sigs[n] == sem) { + MP_TARRAY_REMOVE_AT(cmd->sigs, cmd->num_sigs, n); + return true; + } + } + + return false; +} + +// Attempts to remove a queued signal operation. Returns true if sucessful, +// i.e. the signal could be removed before it ever got fired. +static bool unsignal(struct vk_cmd *cmd, VkSemaphore sem) +{ + if (unsignal_cmd(cmd, sem)) + return true; + + // Attempt to remove it from any queued commands + for (int i = 0; i < cmd->pool->num_cmds_queued; i++) { + if (unsignal_cmd(cmd->pool->cmds_queued[i], sem)) + return true; + } + + return false; +} + +static void release_signal(struct mpvk_ctx *vk, struct vk_signal *sig) +{ + // The semaphore never needs to be recreated, because it's either + // unsignaled while still queued, or unsignaled as a result of a device + // wait. But the event *may* need to be reset, so just always reset it. + vkResetEvent(vk->dev, sig->event); + MP_TARRAY_APPEND(NULL, vk->signals, vk->num_signals, sig); +} + +void vk_cmd_wait(struct mpvk_ctx *vk, struct vk_cmd *cmd, + struct vk_signal **sigptr, VkPipelineStageFlags stage, + VkEvent *out_event) +{ + struct vk_signal *sig = *sigptr; + if (!sig) + return; + + if (out_event && sig->event && sig->event_source == cmd->queue && + unsignal(cmd, sig->semaphore)) + { + // If we can remove the semaphore signal operation from the history and + // pretend it never happened, then we get to use the VkEvent. This also + // requires that the VkEvent was signalled from the same VkQueue. + *out_event = sig->event; + } else if (sig->semaphore) { + // Otherwise, we use the semaphore. (This also unsignals it as a result + // of the command execution) + vk_cmd_dep(cmd, sig->semaphore, stage); + } + + // In either case, once the command completes, we can release the signal + // resource back to the pool. + vk_cmd_callback(cmd, (vk_cb) release_signal, vk, sig); + *sigptr = NULL; +} + const VkImageSubresourceRange vk_range = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .levelCount = 1, diff --git a/video/out/vulkan/utils.h b/video/out/vulkan/utils.h index bdbbe0aa70..538897afae 100644 --- a/video/out/vulkan/utils.h +++ b/video/out/vulkan/utils.h @@ -121,6 +121,35 @@ void vk_cmd_dep(struct vk_cmd *cmd, VkSemaphore dep, VkPipelineStageFlags stage) // after the command completes. void vk_cmd_sig(struct vk_cmd *cmd, VkSemaphore sig); +// Signal abstraction: represents an abstract synchronization mechanism. +// Internally, this may either resolve as a semaphore or an event depending +// on whether the appropriate conditions are met. +struct vk_signal { + VkSemaphore semaphore; + VkEvent event; + VkQueue event_source; +}; + +// Generates a signal after the execution of all previous commands matching the +// given the pipeline stage. The signal is owned by the caller, and must be +// consumed eith vk_cmd_wait or released with vk_signal_cancel in order to +// free the resources. +struct vk_signal *vk_cmd_signal(struct mpvk_ctx *vk, struct vk_cmd *cmd, + VkPipelineStageFlags stage); + +// Consumes a previously generated signal. This signal must fire by the +// indicated stage before the command can run. If *event is not NULL, then it +// MAY be set to a VkEvent which the caller MUST manually wait on in the most +// appropriate way. This function takes over ownership of the signal (and the +// signal will be released/reused automatically) +void vk_cmd_wait(struct mpvk_ctx *vk, struct vk_cmd *cmd, + struct vk_signal **sigptr, VkPipelineStageFlags stage, + VkEvent *out_event); + +// Destroys a currently pending signal, for example if the resource is no +// longer relevant. +void vk_signal_destroy(struct mpvk_ctx *vk, struct vk_signal **sig); + // Command pool / queue family hybrid abstraction struct vk_cmdpool { VkQueueFamilyProperties props; -- cgit v1.2.3