summaryrefslogtreecommitdiffstats
path: root/video/out/vulkan/utils.c
diff options
context:
space:
mode:
authorNiklas Haas <git@haasn.xyz>2017-09-24 15:05:24 +0200
committerMartin Herkt <652892+lachs0r@users.noreply.github.com>2017-12-25 00:47:53 +0100
commitbded247fb53558dd5cba26560d1f24e9234ae24e (patch)
tree1e2c1819fc009acf9eea0d481a003799f7ffda8c /video/out/vulkan/utils.c
parenta3c9685257e60e32646bb54a895ef7574a945f69 (diff)
downloadmpv-bded247fb53558dd5cba26560d1f24e9234ae24e.tar.bz2
mpv-bded247fb53558dd5cba26560d1f24e9234ae24e.tar.xz
vo_gpu: vulkan: support split command pools
Instead of using a single primary queue, we generate multiple vk_cmdpools and pick the right one dynamically based on the intent. This has a number of immediate benefits: 1. We can use async texture uploads 2. We can use the DMA engine for buffer updates 3. We can benefit from async compute on AMD GPUs Unfortunately, the major downside is that due to the lack of QF ownership tracking, we need to use CONCURRENT sharing for all resources (buffers *and* images!). In theory, we could try figuring out a way to get rid of the concurrent sharing for buffers (which is only needed for compute shader UBOs), but even so, the concurrent sharing mode doesn't really seem to have a significant impact over here (nvidia). It's possible that other platforms may disagree. Our deadlock-avoidance strategy is stupidly simple: Just flush the command every time we need to switch queues, and make sure all submission and callbacks happen in FIFO order. This required lifting the cmds_pending and cmds_queued out from vk_cmdpool to mpvk_ctx, and some functions died/got moved as a result, but that's a relatively minor change. On my hardware this is a fairly significant performance boost, mainly due to async transfers. (Nvidia doesn't expose separate compute queues anyway). On AMD, this should be a performance boost as well due to async compute.
Diffstat (limited to 'video/out/vulkan/utils.c')
-rw-r--r--video/out/vulkan/utils.c268
1 files changed, 161 insertions, 107 deletions
diff --git a/video/out/vulkan/utils.c b/video/out/vulkan/utils.c
index 9d9d8d9820..cb73e7d8ac 100644
--- a/video/out/vulkan/utils.c
+++ b/video/out/vulkan/utils.c
@@ -139,7 +139,15 @@ void mpvk_uninit(struct mpvk_ctx *vk)
return;
if (vk->dev) {
- vk_cmdpool_destroy(vk, vk->pool);
+ mpvk_flush_commands(vk);
+ mpvk_poll_commands(vk, UINT64_MAX);
+ assert(vk->num_cmds_queued == 0);
+ assert(vk->num_cmds_pending == 0);
+ talloc_free(vk->cmds_queued);
+ talloc_free(vk->cmds_pending);
+ for (int i = 0; i < vk->num_pools; i++)
+ vk_cmdpool_destroy(vk, vk->pools[i]);
+ talloc_free(vk->pools);
for (int i = 0; i < vk->num_signals; i++)
vk_signal_destroy(vk, &vk->signals[i]);
talloc_free(vk->signals);
@@ -377,6 +385,53 @@ error:
return false;
}
+// Find the most specialized queue supported a combination of flags. In cases
+// where there are multiple queue families at the same specialization level,
+// this finds the one with the most queues. Returns -1 if no queue was found.
+static int find_qf(VkQueueFamilyProperties *qfs, int qfnum, VkQueueFlags flags)
+{
+ int idx = -1;
+ for (int i = 0; i < qfnum; i++) {
+ if (!(qfs[i].queueFlags & flags))
+ continue;
+
+ // QF is more specialized
+ if (idx < 0 || qfs[i].queueFlags < qfs[idx].queueFlags)
+ idx = i;
+
+ // QF has more queues (at the same specialization level)
+ if (qfs[i].queueFlags == qfs[idx].queueFlags &&
+ qfs[i].queueCount > qfs[idx].queueCount)
+ idx = i;
+ }
+
+ return idx;
+}
+
+static void add_qinfo(void *tactx, VkDeviceQueueCreateInfo **qinfos,
+ int *num_qinfos, VkQueueFamilyProperties *qfs, int idx,
+ int qcount)
+{
+ if (idx < 0)
+ return;
+
+ // Check to see if we've already added this queue family
+ for (int i = 0; i < *num_qinfos; i++) {
+ if ((*qinfos)[i].queueFamilyIndex == idx)
+ return;
+ }
+
+ float *priorities = talloc_zero_array(tactx, float, qcount);
+ VkDeviceQueueCreateInfo qinfo = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex = idx,
+ .queueCount = MPMIN(qcount, qfs[idx].queueCount),
+ .pQueuePriorities = priorities,
+ };
+
+ MP_TARRAY_APPEND(tactx, *qinfos, *num_qinfos, qinfo);
+}
+
bool mpvk_device_init(struct mpvk_ctx *vk, struct mpvk_device_opts opts)
{
assert(vk->physd);
@@ -395,45 +450,34 @@ bool mpvk_device_init(struct mpvk_ctx *vk, struct mpvk_device_opts opts)
(unsigned)qfs[i].queueFlags, (int)qfs[i].queueCount);
}
- // For most of our rendering operations, we want to use one "primary" pool,
- // so just pick the queue family with the most features.
- int idx = -1;
- for (int i = 0; i < qfnum; i++) {
- if (!(qfs[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
- continue;
-
- // QF supports more features
- if (idx < 0 || qfs[i].queueFlags > qfs[idx].queueFlags)
- idx = i;
-
- // QF supports more queues (at the same specialization level)
- if (qfs[i].queueFlags == qfs[idx].queueFlags &&
- qfs[i].queueCount > qfs[idx].queueCount)
- {
- idx = i;
- }
- }
+ int idx_gfx = find_qf(qfs, qfnum, VK_QUEUE_GRAPHICS_BIT),
+ idx_comp = find_qf(qfs, qfnum, VK_QUEUE_COMPUTE_BIT),
+ idx_tf = find_qf(qfs, qfnum, VK_QUEUE_TRANSFER_BIT);
// Vulkan requires at least one GRAPHICS queue, so if this fails something
// is horribly wrong.
- assert(idx >= 0);
+ assert(idx_gfx >= 0);
+ MP_VERBOSE(vk, "Using graphics queue (QF %d)\n", idx_gfx);
// Ensure we can actually present to the surface using this queue
VkBool32 sup;
- VK(vkGetPhysicalDeviceSurfaceSupportKHR(vk->physd, idx, vk->surf, &sup));
+ VK(vkGetPhysicalDeviceSurfaceSupportKHR(vk->physd, idx_gfx, vk->surf, &sup));
if (!sup) {
MP_ERR(vk, "Queue family does not support surface presentation!\n");
goto error;
}
+ if (idx_tf >= 0 && idx_tf != idx_gfx)
+ MP_VERBOSE(vk, "Using async transfer (QF %d)\n", idx_tf);
+ if (idx_comp >= 0 && idx_comp != idx_gfx)
+ MP_VERBOSE(vk, "Using async compute (QF %d)\n", idx_comp);
+
// Now that we know which QFs we want, we can create the logical device
- float *priorities = talloc_zero_array(tmp, float, opts.queue_count);
- VkDeviceQueueCreateInfo qinfo = {
- .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
- .queueFamilyIndex = idx,
- .queueCount = MPMIN(qfs[idx].queueCount, opts.queue_count),
- .pQueuePriorities = priorities,
- };
+ VkDeviceQueueCreateInfo *qinfos = NULL;
+ int num_qinfos = 0;
+ add_qinfo(tmp, &qinfos, &num_qinfos, qfs, idx_gfx, opts.queue_count);
+ add_qinfo(tmp, &qinfos, &num_qinfos, qfs, idx_comp, opts.queue_count);
+ add_qinfo(tmp, &qinfos, &num_qinfos, qfs, idx_tf, opts.queue_count);
const char **exts = NULL;
int num_exts = 0;
@@ -443,8 +487,8 @@ bool mpvk_device_init(struct mpvk_ctx *vk, struct mpvk_device_opts opts)
VkDeviceCreateInfo dinfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
- .queueCreateInfoCount = 1,
- .pQueueCreateInfos = &qinfo,
+ .pQueueCreateInfos = qinfos,
+ .queueCreateInfoCount = num_qinfos,
.ppEnabledExtensionNames = exts,
.enabledExtensionCount = num_exts,
};
@@ -455,12 +499,20 @@ bool mpvk_device_init(struct mpvk_ctx *vk, struct mpvk_device_opts opts)
VK(vkCreateDevice(vk->physd, &dinfo, MPVK_ALLOCATOR, &vk->dev));
- vk_malloc_init(vk);
+ // Create the command pools and memory allocator
+ for (int i = 0; i < num_qinfos; i++) {
+ int qf = qinfos[i].queueFamilyIndex;
+ struct vk_cmdpool *pool = vk_cmdpool_create(vk, qinfos[i], qfs[qf]);
+ if (!pool)
+ goto error;
+ MP_TARRAY_APPEND(NULL, vk->pools, vk->num_pools, pool);
+ }
- // Create the command pool(s)
- vk->pool = vk_cmdpool_create(vk, qinfo, qfs[idx]);
- if (!vk->pool)
- goto error;
+ vk->pool_graphics = vk->pools[idx_gfx];
+ vk->pool_compute = idx_comp >= 0 ? vk->pools[idx_comp] : NULL;
+ vk->pool_transfer = idx_tf >= 0 ? vk->pools[idx_tf] : NULL;
+
+ vk_malloc_init(vk);
talloc_free(tmp);
return true;
@@ -563,10 +615,8 @@ static void vk_cmdpool_destroy(struct mpvk_ctx *vk, struct vk_cmdpool *pool)
if (!pool)
return;
- for (int i = 0; i < pool->num_cmds_available; i++)
- vk_cmd_destroy(vk, pool->cmds_available[i]);
- for (int i = 0; i < pool->num_cmds_pending; i++)
- vk_cmd_destroy(vk, pool->cmds_pending[i]);
+ for (int i = 0; i < pool->num_cmds; i++)
+ vk_cmd_destroy(vk, pool->cmds[i]);
vkDestroyCommandPool(vk->dev, pool->pool, MPVK_ALLOCATOR);
talloc_free(pool);
@@ -603,26 +653,67 @@ error:
return NULL;
}
-void mpvk_pool_wait_cmds(struct mpvk_ctx *vk, struct vk_cmdpool *pool,
- uint64_t timeout)
+void mpvk_poll_commands(struct mpvk_ctx *vk, uint64_t timeout)
{
- if (!pool)
- return;
-
- while (pool->num_cmds_pending > 0) {
- struct vk_cmd *cmd = pool->cmds_pending[0];
+ while (vk->num_cmds_pending > 0) {
+ struct vk_cmd *cmd = vk->cmds_pending[0];
+ struct vk_cmdpool *pool = cmd->pool;
VkResult res = vk_cmd_poll(vk, cmd, timeout);
if (res == VK_TIMEOUT)
break;
vk_cmd_reset(vk, cmd);
- MP_TARRAY_REMOVE_AT(pool->cmds_pending, pool->num_cmds_pending, 0);
- MP_TARRAY_APPEND(pool, pool->cmds_available, pool->num_cmds_available, cmd);
+ MP_TARRAY_REMOVE_AT(vk->cmds_pending, vk->num_cmds_pending, 0);
+ MP_TARRAY_APPEND(pool, pool->cmds, pool->num_cmds, cmd);
}
}
-void mpvk_dev_wait_cmds(struct mpvk_ctx *vk, uint64_t timeout)
+bool mpvk_flush_commands(struct mpvk_ctx *vk)
{
- mpvk_pool_wait_cmds(vk, vk->pool, timeout);
+ bool ret = true;
+
+ for (int i = 0; i < vk->num_cmds_queued; i++) {
+ struct vk_cmd *cmd = vk->cmds_queued[i];
+ struct vk_cmdpool *pool = cmd->pool;
+
+ VkSubmitInfo sinfo = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cmd->buf,
+ .waitSemaphoreCount = cmd->num_deps,
+ .pWaitSemaphores = cmd->deps,
+ .pWaitDstStageMask = cmd->depstages,
+ .signalSemaphoreCount = cmd->num_sigs,
+ .pSignalSemaphores = cmd->sigs,
+ };
+
+ VK(vkQueueSubmit(cmd->queue, 1, &sinfo, cmd->fence));
+ MP_TARRAY_APPEND(NULL, vk->cmds_pending, vk->num_cmds_pending, cmd);
+
+ if (mp_msg_test(vk->log, MSGL_TRACE)) {
+ MP_TRACE(vk, "Submitted command on queue %p (QF %d):\n",
+ (void *)cmd->queue, pool->qf);
+ for (int n = 0; n < cmd->num_deps; n++)
+ MP_TRACE(vk, " waits on semaphore %p\n", (void *)cmd->deps[n]);
+ for (int n = 0; n < cmd->num_sigs; n++)
+ MP_TRACE(vk, " signals semaphore %p\n", (void *)cmd->sigs[n]);
+ }
+ continue;
+
+error:
+ vk_cmd_reset(vk, cmd);
+ MP_TARRAY_APPEND(pool, pool->cmds, pool->num_cmds, cmd);
+ ret = false;
+ }
+
+ vk->num_cmds_queued = 0;
+
+ // Rotate the queues to ensure good parallelism across frames
+ for (int i = 0; i < vk->num_pools; i++) {
+ struct vk_cmdpool *pool = vk->pools[i];
+ pool->idx_queues = (pool->idx_queues + 1) % pool->num_queues;
+ }
+
+ return ret;
}
void vk_dev_callback(struct mpvk_ctx *vk, vk_cb callback, void *p, void *arg)
@@ -639,10 +730,10 @@ struct vk_cmd *vk_cmd_begin(struct mpvk_ctx *vk, struct vk_cmdpool *pool)
{
// garbage collect the cmdpool first, to increase the chances of getting
// an already-available command buffer
- mpvk_pool_wait_cmds(vk, pool, 0);
+ mpvk_poll_commands(vk, 0);
struct vk_cmd *cmd = NULL;
- if (MP_TARRAY_POP(pool->cmds_available, pool->num_cmds_available, &cmd))
+ if (MP_TARRAY_POP(pool->cmds, pool->num_cmds, &cmd))
goto done;
// No free command buffers => allocate another one
@@ -675,58 +766,13 @@ void vk_cmd_queue(struct mpvk_ctx *vk, struct vk_cmd *cmd)
VK(vkEndCommandBuffer(cmd->buf));
VK(vkResetFences(vk->dev, 1, &cmd->fence));
- MP_TARRAY_APPEND(pool, pool->cmds_queued, pool->num_cmds_queued, cmd);
+ MP_TARRAY_APPEND(NULL, vk->cmds_queued, vk->num_cmds_queued, cmd);
vk->last_cmd = cmd;
return;
error:
vk_cmd_reset(vk, cmd);
- MP_TARRAY_APPEND(pool, pool->cmds_available, pool->num_cmds_available, cmd);
-}
-
-bool vk_flush_commands(struct mpvk_ctx *vk)
-{
- bool ret = true;
-
- struct vk_cmdpool *pool = vk->pool;
- for (int i = 0; i < pool->num_cmds_queued; i++) {
- struct vk_cmd *cmd = pool->cmds_queued[i];
-
- VkSubmitInfo sinfo = {
- .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
- .commandBufferCount = 1,
- .pCommandBuffers = &cmd->buf,
- .waitSemaphoreCount = cmd->num_deps,
- .pWaitSemaphores = cmd->deps,
- .pWaitDstStageMask = cmd->depstages,
- .signalSemaphoreCount = cmd->num_sigs,
- .pSignalSemaphores = cmd->sigs,
- };
-
- VK(vkQueueSubmit(cmd->queue, 1, &sinfo, cmd->fence));
- MP_TARRAY_APPEND(pool, pool->cmds_pending, pool->num_cmds_pending, cmd);
-
- if (mp_msg_test(vk->log, MSGL_TRACE)) {
- MP_TRACE(vk, "Submitted command on queue %p (QF %d):\n",
- (void *)cmd->queue, pool->qf);
- for (int n = 0; n < cmd->num_deps; n++)
- MP_TRACE(vk, " waits on semaphore %p\n", (void *)cmd->deps[n]);
- for (int n = 0; n < cmd->num_sigs; n++)
- MP_TRACE(vk, " signals semaphore %p\n", (void *)cmd->sigs[n]);
- }
- continue;
-
-error:
- vk_cmd_reset(vk, cmd);
- MP_TARRAY_APPEND(pool, pool->cmds_available, pool->num_cmds_available, cmd);
- ret = false;
- }
-
- pool->num_cmds_queued = 0;
-
- // Rotate the queues to ensure good parallelism across frames
- pool->idx_queues = (pool->idx_queues + 1) % pool->num_queues;
- return ret;
+ MP_TARRAY_APPEND(pool, pool->cmds, pool->num_cmds, cmd);
}
void vk_signal_destroy(struct mpvk_ctx *vk, struct vk_signal **sig)
@@ -762,10 +808,16 @@ struct vk_signal *vk_cmd_signal(struct mpvk_ctx *vk, struct vk_cmd *cmd,
VK(vkCreateEvent(vk->dev, &einfo, MPVK_ALLOCATOR, &sig->event));
done:
- // Signal both the semaphore and the event. (We will only end up using one)
+ // Signal both the semaphore and the event if possible. (We will only
+ // end up using one or the other)
vk_cmd_sig(cmd, sig->semaphore);
- vkCmdSetEvent(cmd->buf, sig->event, stage);
- sig->event_source = cmd->queue;
+
+ VkQueueFlags req = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT;
+ if (cmd->pool->props.queueFlags & req) {
+ vkCmdSetEvent(cmd->buf, sig->event, stage);
+ sig->event_source = cmd->queue;
+ }
+
return sig;
error:
@@ -787,14 +839,14 @@ static bool unsignal_cmd(struct vk_cmd *cmd, VkSemaphore sem)
// 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)
+static bool unsignal(struct mpvk_ctx *vk, 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))
+ for (int i = 0; i < vk->num_cmds_queued; i++) {
+ if (unsignal_cmd(vk->cmds_queued[i], sem))
return true;
}
@@ -806,7 +858,9 @@ 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);
+ if (sig->event_source)
+ vkResetEvent(vk->dev, sig->event);
+ sig->event_source = NULL;
MP_TARRAY_APPEND(NULL, vk->signals, vk->num_signals, sig);
}
@@ -819,7 +873,7 @@ void vk_cmd_wait(struct mpvk_ctx *vk, struct vk_cmd *cmd,
return;
if (out_event && sig->event && sig->event_source == cmd->queue &&
- unsignal(cmd, sig->semaphore))
+ unsignal(vk, 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