0
0
mirror of https://github.com/mpv-player/mpv.git synced 2024-09-20 20:03:10 +02:00

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).
This commit is contained in:
Niklas Haas 2017-09-29 00:36:46 +02:00 committed by Martin Herkt
parent 5feaaba0fd
commit f2f91cf570
3 changed files with 144 additions and 0 deletions

View File

@ -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;
};

View File

@ -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,

View File

@ -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;