Commit e1016179 authored by Niklas Haas's avatar Niklas Haas

vulkan: add external VkImage interop API

This is essentially the same interface that's used between pl_gpu and
the vulkan swapchain implementation, tidied up a bit and exopsed to the
user.

This required tying off some loose ends related to queue families in
order to make sure the behavior is defined.

Closes #22.
parent 316bfbc7
......@@ -78,6 +78,17 @@ struct pl_vulkan {
// extensions enabled by libplacebo internally. May contain duplicates.
const char **extensions;
int num_extensions;
// The list of enabled queue families and their queue counts. This may
// include secondary queue families providing compute or transfer
// capabilities.
const struct pl_vulkan_queue *queues;
int num_queues;
};
struct pl_vulkan_queue {
int index; // Queue family index
int count; // Queue family count
};
struct pl_vulkan_params {
......@@ -200,4 +211,51 @@ struct pl_vulkan_swapchain_params {
const struct pl_swapchain *pl_vulkan_create_swapchain(const struct pl_vulkan *vk,
const struct pl_vulkan_swapchain_params *params);
// VkImage interop API
// Wraps an external VkImage into a pl_tex abstraction. By default, the image
// is considered "held" by the user and will not be touched or used by
// libplacebo until released (see `pl_vulkan_release`). Releasing an image
// makes libplacebo take ownership of the image until the user calls
// `pl_vulkan_hold` on it again. During this time, the user should not use
// the VkImage in any way.
//
// This wrapper can be destroyed by simply calling `pl_tex_destroy` on it,
// which will not destroy the underlying VkImage. If a pl_tex wrapper is
// destroyed while an image is not currently being held by the user, that
// image is left in an undefined state.
//
// Wrapping the same VkImage multiple times is undefined behavior, as is trying
// to wrap an image belonging to a different VkDevice than the one in use by
// `gpu`.
//
// The VkImage must be usable concurrently by all of the queue family indices
// listed in `pl_vulkan->queues`. Note that this requires the use of
// VK_SHARING_MODE_CONCURRENT if `pl_vulkan->num_queues` is greater than 1. If
// this is difficult to achieve for the user, then `async_transfer` /
// `async_compute` should be turned off, which guarantees the use of only one
// queue family.
//
// This function may fail, for example if the VkFormat specified does not
// map to any corresponding `pl_fmt`.
const struct pl_tex *pl_vulkan_wrap(const struct pl_gpu *gpu,
VkImage image, int w, int h, int d,
VkFormat format, VkImageUsageFlags usage);
// "Hold" an external image. This will transition the image into the layout and
// access mode specified by the user, and fire the given semaphore when this is
// done. This marks the image as held. Attempting to perform any pl_tex_*
// operation (except pl_tex_destroy) on a held image is undefined behavior.
void pl_vulkan_hold(const struct pl_gpu *gpu, const struct pl_tex *tex,
VkImageLayout layout, VkAccessFlags access,
VkSemaphore sem_out);
// "Release" an external image. `layout` and `access` describe the current
// state of the image at the point in time when the user is releasing it. If
// `sem_in` is specified, it must fire before libplacebo will actually use or
// modify the image.
void pl_vulkan_release(const struct pl_gpu *gpu, const struct pl_tex *tex,
VkImageLayout layout, VkAccessFlags access,
VkSemaphore sem_in);
#endif // LIBPLACEBO_VULKAN_H_
......@@ -570,6 +570,16 @@ const struct pl_vulkan *pl_vulkan_create(struct pl_context *ctx,
pl_vk->device = vk->dev;
pl_vk->extensions = vk->exts;
pl_vk->num_extensions = vk->num_exts;
pl_vk->num_queues = vk->num_pools;
pl_vk->queues = talloc_array(pl_vk, struct pl_vulkan_queue, vk->num_pools);
for (int i = 0; i < vk->num_pools; i++) {
struct pl_vulkan_queue *queues = (struct pl_vulkan_queue *) pl_vk->queues;
queues[i] = (struct pl_vulkan_queue) {
.index = vk->pools[i]->qf,
.count = vk->pools[i]->num_queues,
};
}
return pl_vk;
error:
......
......@@ -338,6 +338,7 @@ static VkResult vk_create_render_pass(VkDevice dev, const struct pl_fmt *fmt,
// For pl_tex.priv
struct pl_tex_vk {
bool held;
bool external_img;
bool may_invalidate;
enum queue_type transfer_queue;
......@@ -369,6 +370,9 @@ struct pl_tex_vk {
void pl_tex_vk_external_dep(const struct pl_gpu *gpu, const struct pl_tex *tex,
VkSemaphore external_dep)
{
if (!external_dep)
return;
struct pl_tex_vk *tex_vk = tex->priv;
TARRAY_APPEND(tex_vk, tex_vk->ext_deps, tex_vk->num_ext_deps, external_dep);
}
......@@ -381,6 +385,7 @@ static void tex_barrier(const struct pl_gpu *gpu, struct vk_cmd *cmd,
{
struct vk_ctx *vk = pl_vk_get(gpu);
struct pl_tex_vk *tex_vk = tex->priv;
assert(!tex_vk->held);
for (int i = 0; i < tex_vk->num_ext_deps; i++)
vk_cmd_dep(cmd, tex_vk->ext_deps[i], stage);
......@@ -843,44 +848,48 @@ static void vk_tex_blit(const struct pl_gpu *gpu,
tex_signal(gpu, cmd, dst, VK_PIPELINE_STAGE_TRANSFER_BIT);
}
const struct pl_tex *pl_vk_wrap_swimg(const struct pl_gpu *gpu, VkImage vkimg,
VkSwapchainCreateInfoKHR info)
const struct pl_tex *pl_vulkan_wrap(const struct pl_gpu *gpu,
VkImage image, int w, int h, int d,
VkFormat imageFormat,
VkImageUsageFlags imageUsage)
{
struct pl_tex *tex = NULL;
const struct pl_fmt *format = NULL;
for (int i = 0; i < gpu->num_formats; i++) {
const struct vk_format *fmt = gpu->formats[i]->priv;
if (fmt->ifmt == info.imageFormat) {
if (fmt->ifmt == imageFormat) {
format = gpu->formats[i];
break;
}
}
if (!format) {
PL_ERR(gpu, "Could not find pl_fmt suitable for wrapped swchain image "
"with surface format 0x%x\n", (unsigned) info.imageFormat);
PL_ERR(gpu, "Could not find pl_fmt suitable for wrapped image "
"with VkFormat 0x%x\n", (unsigned) imageFormat);
goto error;
}
tex = talloc_zero(NULL, struct pl_tex);
tex->params = (struct pl_tex_params) {
.format = format,
.w = info.imageExtent.width,
.h = info.imageExtent.height,
.sampleable = !!(info.imageUsage & VK_IMAGE_USAGE_SAMPLED_BIT),
.renderable = !!(info.imageUsage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
.storable = !!(info.imageUsage & VK_IMAGE_USAGE_STORAGE_BIT),
.blit_src = !!(info.imageUsage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
.blit_dst = !!(info.imageUsage & VK_IMAGE_USAGE_TRANSFER_DST_BIT),
.host_writable = !!(info.imageUsage & VK_IMAGE_USAGE_TRANSFER_DST_BIT),
.host_readable = !!(info.imageUsage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
.w = w,
.h = h,
.d = d,
.sampleable = !!(imageUsage & VK_IMAGE_USAGE_SAMPLED_BIT),
.renderable = !!(imageUsage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
.storable = !!(imageUsage & VK_IMAGE_USAGE_STORAGE_BIT),
.blit_src = !!(imageUsage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
.blit_dst = !!(imageUsage & VK_IMAGE_USAGE_TRANSFER_DST_BIT),
.host_writable = !!(imageUsage & VK_IMAGE_USAGE_TRANSFER_DST_BIT),
.host_readable = !!(imageUsage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
};
struct pl_tex_vk *tex_vk = tex->priv = talloc_zero(tex, struct pl_tex_vk);
tex_vk->type = VK_IMAGE_TYPE_2D;
tex_vk->external_img = true;
tex_vk->img = vkimg;
tex_vk->held = true;
tex_vk->img = image;
if (!vk_init_image(gpu, tex))
goto error;
......@@ -892,6 +901,41 @@ error:
return NULL;
}
void pl_vulkan_hold(const struct pl_gpu *gpu, const struct pl_tex *tex,
VkImageLayout layout, VkAccessFlags access,
VkSemaphore sem_out)
{
struct pl_tex_vk *tex_vk = tex->priv;
pl_assert(tex_vk->external_img);
pl_assert(!tex_vk->held);
pl_assert(sem_out);
struct vk_cmd *cmd = vk_require_cmd(gpu, GRAPHICS);
if (!cmd) {
PL_ERR(gpu, "Failed holding external image!");
return;
}
tex_barrier(gpu, cmd, tex, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
access, layout);
vk_cmd_sig(cmd, sem_out);
pl_gpu_flush(gpu);
tex_vk->held = true;
}
void pl_vulkan_release(const struct pl_gpu *gpu, const struct pl_tex *tex,
VkImageLayout layout, VkAccessFlags access,
VkSemaphore sem_in)
{
struct pl_tex_vk *tex_vk = tex->priv;
pl_assert(tex_vk->held);
pl_tex_vk_external_dep(gpu, tex, sem_in);
tex_vk->current_layout = layout;
tex_vk->current_access = access;
tex_vk->held = false;
}
// For pl_buf.priv
struct pl_buf_vk {
struct vk_bufslice slice;
......@@ -2173,20 +2217,10 @@ static void vk_gpu_flush(const struct pl_gpu *gpu)
vk_flush_commands(vk);
}
struct vk_cmd *pl_vk_finish_frame(const struct pl_gpu *gpu,
const struct pl_tex *tex)
struct vk_cmd *pl_vk_steal_cmd(const struct pl_gpu *gpu)
{
struct pl_vk *p = gpu->priv;
struct vk_cmd *cmd = vk_require_cmd(gpu, GRAPHICS);
if (!cmd)
return NULL;
struct pl_tex_vk *tex_vk = tex->priv;
pl_assert(tex_vk->external_img);
tex_barrier(gpu, cmd, tex, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
VK_ACCESS_MEMORY_READ_BIT, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
// Return this directly instead of going through vk_submit
p->cmd = NULL;
return cmd;
}
......
......@@ -28,20 +28,12 @@ const struct pl_gpu *pl_gpu_create_vk(struct vk_ctx *vk);
// a vulkan ra.
struct vk_ctx *pl_vk_get(const struct pl_gpu *gpu);
// Allocates a pl_tex that wraps a swapchain image. The contents of the image
// will be invalidated, and access to it will only be internally synchronized.
// So the calling could should not do anything else with the VkImage.
const struct pl_tex *pl_vk_wrap_swimg(const struct pl_gpu *gpu, VkImage vkimg,
VkSwapchainCreateInfoKHR info);
// Associates an external semaphore (dependency) with a pl_tex, such that this
// pl_tex will not be used by the pl_vk until the external semaphore fires.
void pl_tex_vk_external_dep(const struct pl_gpu *gpu, const struct pl_tex *tex,
VkSemaphore external_dep);
// This function finalizes rendering, transitions `tex` (which must be a
// wrapped swapchain image) into a format suitable for presentation, and returns
// the resulting command buffer (or NULL on error). The caller may add their
// own semaphores to this command buffer, and must submit it afterwards.
struct vk_cmd *pl_vk_finish_frame(const struct pl_gpu *gpu,
const struct pl_tex *tex);
// This function takes the current graphics command and steals it from the
// GPU, so the caller can do custom vk_cmd_ calls on it. The caller should
// submit it as well.
struct vk_cmd *pl_vk_steal_cmd(const struct pl_gpu *gpu);
......@@ -375,7 +375,9 @@ static bool vk_sw_recreate(const struct pl_swapchain *sw)
p->num_images = num_images;
TARRAY_GROW(p, p->images, num_images);
for (int i = 0; i < num_images; i++) {
p->images[i] = pl_vk_wrap_swimg(gpu, vkimages[i], sinfo);
const VkExtent2D *ext = &sinfo.imageExtent;
p->images[i] = pl_vulkan_wrap(gpu, vkimages[i], ext->width, ext->height,
0, sinfo.imageFormat, sinfo.imageUsage);
if (!p->images[i])
goto error;
}
......@@ -424,13 +426,14 @@ static bool vk_sw_start_frame(const struct pl_swapchain *sw,
switch (res) {
case VK_SUCCESS:
p->last_imgidx = imgidx;
pl_vulkan_release(sw->gpu, p->images[imgidx],
VK_IMAGE_LAYOUT_UNDEFINED, 0, sem_in);
*out_frame = (struct pl_swapchain_frame) {
.fbo = p->images[imgidx],
.flipped = false,
.color_repr = p->color_repr,
.color_space = p->color_space,
};
pl_tex_vk_external_dep(sw->gpu, out_frame->fbo, sem_in);
return true;
case VK_ERROR_OUT_OF_DATE_KHR: {
......@@ -464,13 +467,16 @@ static bool vk_sw_submit_frame(const struct pl_swapchain *sw)
if (!p->swapchain)
return false;
struct vk_cmd *cmd = pl_vk_finish_frame(gpu, p->images[p->last_imgidx]);
if (!cmd)
return false;
VkSemaphore sem_out = p->sems_out[p->idx_sems++];
p->idx_sems %= p->num_sems;
vk_cmd_sig(cmd, sem_out);
pl_vulkan_hold(gpu, p->images[p->last_imgidx],
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_ACCESS_MEMORY_READ_BIT, sem_out);
struct vk_cmd *cmd = pl_vk_steal_cmd(gpu);
if (!cmd)
return false;
p->frames_in_flight++;
vk_cmd_callback(cmd, (vk_cb) present_cb, p, NULL);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment