summaryrefslogtreecommitdiffstats
path: root/video/out/vulkan/context.c
diff options
context:
space:
mode:
authorNiklas Haas <git@haasn.xyz>2018-11-10 12:53:33 +0100
committerJan Ekström <jeebjp@gmail.com>2019-04-21 23:55:22 +0300
commit7006d6752d7da21870dfdb2b0d7640a3734f748c (patch)
tree035ca58d22de438e834d212e97b73d03a4248d98 /video/out/vulkan/context.c
parent9f7dcc0726ab635fb34fb7310e54b1aec9467f14 (diff)
downloadmpv-7006d6752d7da21870dfdb2b0d7640a3734f748c.tar.bz2
mpv-7006d6752d7da21870dfdb2b0d7640a3734f748c.tar.xz
vo_gpu: vulkan: use libplacebo instead
This commit rips out the entire mpv vulkan implementation in favor of exposing lightweight wrappers on top of libplacebo instead, which provides much of the same except in a more up-to-date and polished form. This (finally) unifies the code base between mpv and libplacebo, which is something I've been hoping to do for a long time. Note: The ra_pl wrappers are abstract enough from the actual libplacebo device type that we can in theory re-use them for other devices like d3d11 or even opengl in the future, so I moved them to a separate directory for the time being. However, the rest of the code is still vulkan-specific, so I've kept the "vulkan" naming and file paths, rather than introducing a new `--gpu-api` type. (Which would have been ended up with significantly more code duplicaiton) Plus, the code and functionality is similar enough that for most users this should just be a straight-up drop-in replacement. Note: This commit excludes some changes; specifically, the updates to context_win and hwdec_cuda are deferred to separate commits for authorship reasons.
Diffstat (limited to 'video/out/vulkan/context.c')
-rw-r--r--video/out/vulkan/context.c482
1 files changed, 68 insertions, 414 deletions
diff --git a/video/out/vulkan/context.c b/video/out/vulkan/context.c
index 29a2c9b727..c05a5ac209 100644
--- a/video/out/vulkan/context.c
+++ b/video/out/vulkan/context.c
@@ -16,25 +16,17 @@
*/
#include "options/m_config.h"
-#include "video/out/gpu/spirv.h"
+#include "video/out/placebo/ra_pl.h"
#include "context.h"
-#include "ra_vk.h"
#include "utils.h"
-enum {
- SWAP_AUTO = 0,
- SWAP_FIFO,
- SWAP_FIFO_RELAXED,
- SWAP_MAILBOX,
- SWAP_IMMEDIATE,
- SWAP_COUNT,
-};
-
struct vulkan_opts {
- struct mpvk_device_opts dev_opts; // logical device options
char *device; // force a specific GPU
int swap_mode;
+ int queue_count;
+ int async_transfer;
+ int async_compute;
};
static int vk_validate_dev(struct mp_log *log, const struct m_option *opt,
@@ -52,7 +44,7 @@ static int vk_validate_dev(struct mp_log *log, const struct m_option *opt,
VkPhysicalDevice *devices = NULL;
uint32_t num = 0;
- res = vkCreateInstance(&info, MPVK_ALLOCATOR, &inst);
+ res = vkCreateInstance(&info, NULL, &inst);
if (res != VK_SUCCESS)
goto done;
@@ -97,45 +89,30 @@ const struct m_sub_options vulkan_conf = {
.opts = (const struct m_option[]) {
OPT_STRING_VALIDATE("vulkan-device", device, 0, vk_validate_dev),
OPT_CHOICE("vulkan-swap-mode", swap_mode, 0,
- ({"auto", SWAP_AUTO},
- {"fifo", SWAP_FIFO},
- {"fifo-relaxed", SWAP_FIFO_RELAXED},
- {"mailbox", SWAP_MAILBOX},
- {"immediate", SWAP_IMMEDIATE})),
- OPT_INTRANGE("vulkan-queue-count", dev_opts.queue_count, 0, 1, 8,
- OPTDEF_INT(1)),
- OPT_FLAG("vulkan-async-transfer", dev_opts.async_transfer, 0),
- OPT_FLAG("vulkan-async-compute", dev_opts.async_compute, 0),
+ ({"auto", -1},
+ {"fifo", VK_PRESENT_MODE_FIFO_KHR},
+ {"fifo-relaxed", VK_PRESENT_MODE_FIFO_RELAXED_KHR},
+ {"mailbox", VK_PRESENT_MODE_MAILBOX_KHR},
+ {"immediate", VK_PRESENT_MODE_IMMEDIATE_KHR})),
+ OPT_INTRANGE("vulkan-queue-count", queue_count, 0, 1, 8),
+ OPT_FLAG("vulkan-async-transfer", async_transfer, 0),
+ OPT_FLAG("vulkan-async-compute", async_compute, 0),
{0}
},
.size = sizeof(struct vulkan_opts),
.defaults = &(struct vulkan_opts) {
- .dev_opts = {
- .async_transfer = 1,
- },
+ .swap_mode = -1,
+ .queue_count = 1,
+ .async_transfer = true,
+ .async_compute = true,
},
};
struct priv {
struct mpvk_ctx *vk;
struct vulkan_opts *opts;
- // Swapchain metadata:
- int w, h; // current size
- VkSwapchainCreateInfoKHR protoInfo; // partially filled-in prototype
- VkSwapchainKHR swapchain;
- VkSwapchainKHR old_swapchain;
- int frames_in_flight;
- // state of the images:
- struct ra_tex **images; // ra_tex wrappers for the vkimages
- int num_images; // size of images
- VkSemaphore *sems_in; // pool of semaphores used to synchronize images
- VkSemaphore *sems_out; // outgoing semaphores (rendering complete)
- int num_sems;
- int idx_sems; // index of next free semaphore pair
- int last_imgidx; // the image index last acquired (for submit)
-
- // This is used to pre-fetch the next frame at the end of swap_buffers
- struct ra_fbo queued_fbo;
+ const struct pl_swapchain *swapchain;
+ struct ra_tex proxy_tex;
};
static const struct ra_swapchain_fns vulkan_swapchain;
@@ -149,133 +126,26 @@ struct mpvk_ctx *ra_vk_ctx_get(struct ra_ctx *ctx)
return p->vk;
}
-static bool update_swapchain_info(struct priv *p,
- VkSwapchainCreateInfoKHR *info)
-{
- struct mpvk_ctx *vk = p->vk;
-
- // Query the supported capabilities and update this struct as needed
- VkSurfaceCapabilitiesKHR caps;
- VK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk->physd, vk->surf, &caps));
-
- // Sorted by preference
- static const VkCompositeAlphaFlagsKHR alphaModes[] = {
- VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
- VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
- };
-
- for (int i = 0; i < MP_ARRAY_SIZE(alphaModes); i++) {
- if (caps.supportedCompositeAlpha & alphaModes[i]) {
- info->compositeAlpha = alphaModes[i];
- break;
- }
- }
-
- if (!info->compositeAlpha) {
- MP_ERR(vk, "Failed picking alpha compositing mode (caps: 0x%x)\n",
- caps.supportedCompositeAlpha);
- goto error;
- }
-
- static const VkSurfaceTransformFlagsKHR rotModes[] = {
- VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
- VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR,
- };
-
- for (int i = 0; i < MP_ARRAY_SIZE(rotModes); i++) {
- if (caps.supportedTransforms & rotModes[i]) {
- info->preTransform = rotModes[i];
- break;
- }
- }
-
- if (!info->preTransform) {
- MP_ERR(vk, "Failed picking surface transform mode (caps: 0x%x)\n",
- caps.supportedTransforms);
- goto error;
- }
-
- // Image count as required
- MP_VERBOSE(vk, "Requested image count: %d (min %d max %d)\n",
- (int)info->minImageCount, (int)caps.minImageCount,
- (int)caps.maxImageCount);
-
- info->minImageCount = MPMAX(info->minImageCount, caps.minImageCount);
- if (caps.maxImageCount)
- info->minImageCount = MPMIN(info->minImageCount, caps.maxImageCount);
-
- // Check the extent against the allowed parameters
- if (caps.currentExtent.width != info->imageExtent.width &&
- caps.currentExtent.width != 0xFFFFFFFF)
- {
- MP_WARN(vk, "Requested width %d does not match current width %d\n",
- (int)info->imageExtent.width, (int)caps.currentExtent.width);
- info->imageExtent.width = caps.currentExtent.width;
- }
-
- if (caps.currentExtent.height != info->imageExtent.height &&
- caps.currentExtent.height != 0xFFFFFFFF)
- {
- MP_WARN(vk, "Requested height %d does not match current height %d\n",
- (int)info->imageExtent.height, (int)caps.currentExtent.height);
- info->imageExtent.height = caps.currentExtent.height;
- }
-
- if (caps.minImageExtent.width > info->imageExtent.width ||
- caps.minImageExtent.height > info->imageExtent.height)
- {
- MP_ERR(vk, "Requested size %dx%d smaller than device minimum %d%d\n",
- (int)info->imageExtent.width, (int)info->imageExtent.height,
- (int)caps.minImageExtent.width, (int)caps.minImageExtent.height);
- goto error;
- }
-
- if (caps.maxImageExtent.width < info->imageExtent.width ||
- caps.maxImageExtent.height < info->imageExtent.height)
- {
- MP_ERR(vk, "Requested size %dx%d larger than device maximum %d%d\n",
- (int)info->imageExtent.width, (int)info->imageExtent.height,
- (int)caps.maxImageExtent.width, (int)caps.maxImageExtent.height);
- goto error;
- }
-
- // We just request whatever usage we can, and let the ra_vk decide what
- // ra_tex_params that translates to. This makes the images as flexible
- // as possible.
- info->imageUsage = caps.supportedUsageFlags;
- return true;
-
-error:
- return false;
-}
-
void ra_vk_ctx_uninit(struct ra_ctx *ctx)
{
- if (ctx->ra) {
- struct priv *p = ctx->swapchain->priv;
- struct mpvk_ctx *vk = p->vk;
-
- mpvk_flush_commands(vk);
- mpvk_poll_commands(vk, UINT64_MAX);
+ if (!ctx->swapchain)
+ return;
- for (int i = 0; i < p->num_images; i++)
- ra_tex_free(ctx->ra, &p->images[i]);
- for (int i = 0; i < p->num_sems; i++) {
- vkDestroySemaphore(vk->dev, p->sems_in[i], MPVK_ALLOCATOR);
- vkDestroySemaphore(vk->dev, p->sems_out[i], MPVK_ALLOCATOR);
- }
+ struct priv *p = ctx->swapchain->priv;
+ struct mpvk_ctx *vk = p->vk;
- vkDestroySwapchainKHR(vk->dev, p->swapchain, MPVK_ALLOCATOR);
+ if (ctx->ra) {
+ pl_gpu_finish(vk->gpu);
+ pl_swapchain_destroy(&p->swapchain);
ctx->ra->fns->destroy(ctx->ra);
ctx->ra = NULL;
}
- talloc_free(ctx->swapchain);
- ctx->swapchain = NULL;
+ vk->gpu = NULL;
+ pl_vulkan_destroy(&vk->vulkan);
+ TA_FREEP(&ctx->swapchain);
}
-static const struct ra_swapchain_fns vulkan_swapchain;
-
bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
VkPresentModeKHR preferred_mode)
{
@@ -287,56 +157,36 @@ bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
p->vk = vk;
p->opts = mp_get_config_group(p, ctx->global, &vulkan_conf);
- if (!mpvk_find_phys_device(vk, p->opts->device, ctx->opts.allow_sw))
- goto error;
- if (!spirv_compiler_init(ctx))
- goto error;
- vk->spirv = ctx->spirv;
- if (!mpvk_pick_surface_format(vk))
- goto error;
- if (!mpvk_device_init(vk, p->opts->dev_opts))
+ assert(vk->ctx);
+ assert(vk->vkinst);
+ vk->vulkan = pl_vulkan_create(vk->ctx, &(struct pl_vulkan_params) {
+ .instance = vk->vkinst->instance,
+ .surface = vk->surface,
+ .async_transfer = p->opts->async_transfer,
+ .async_compute = p->opts->async_compute,
+ .queue_count = p->opts->queue_count,
+ });
+ if (!vk->vulkan)
goto error;
- ctx->ra = ra_create_vk(vk, ctx->log);
+ vk->gpu = vk->vulkan->gpu;
+ ctx->ra = ra_create_pl(vk->gpu, ctx->log);
if (!ctx->ra)
goto error;
- static const VkPresentModeKHR present_modes[SWAP_COUNT] = {
- [SWAP_FIFO] = VK_PRESENT_MODE_FIFO_KHR,
- [SWAP_FIFO_RELAXED] = VK_PRESENT_MODE_FIFO_RELAXED_KHR,
- [SWAP_MAILBOX] = VK_PRESENT_MODE_MAILBOX_KHR,
- [SWAP_IMMEDIATE] = VK_PRESENT_MODE_IMMEDIATE_KHR,
+ // Create the swapchain
+ struct pl_vulkan_swapchain_params params = {
+ .surface = vk->surface,
+ .present_mode = preferred_mode,
+ .swapchain_depth = ctx->opts.swapchain_depth,
};
- p->protoInfo = (VkSwapchainCreateInfoKHR) {
- .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
- .surface = vk->surf,
- .imageFormat = vk->surf_format.format,
- .imageColorSpace = vk->surf_format.colorSpace,
- .imageArrayLayers = 1, // non-stereoscopic
- .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
- .minImageCount = ctx->opts.swapchain_depth + 1, // +1 for FB
- .presentMode = p->opts->swap_mode ? present_modes[p->opts->swap_mode]
- : preferred_mode,
- .clipped = true,
- };
+ if (p->opts->swap_mode >= 0) // user override
+ params.present_mode = p->opts->swap_mode;
- // Make sure the swapchain present mode is supported
- int num_modes;
- VK(vkGetPhysicalDeviceSurfacePresentModesKHR(vk->physd, vk->surf,
- &num_modes, NULL));
- VkPresentModeKHR *modes = talloc_array(NULL, VkPresentModeKHR, num_modes);
- VK(vkGetPhysicalDeviceSurfacePresentModesKHR(vk->physd, vk->surf,
- &num_modes, modes));
- bool supported = false;
- for (int i = 0; i < num_modes; i++)
- supported |= (modes[i] == p->protoInfo.presentMode);
- talloc_free(modes);
-
- if (!supported) {
- MP_ERR(ctx, "Requested swap mode unsupported by this device!\n");
+ p->swapchain = pl_vulkan_create_swapchain(vk->vulkan, &params);
+ if (!p->swapchain)
goto error;
- }
return true;
@@ -345,245 +195,49 @@ error:
return false;
}
-static void destroy_swapchain(struct mpvk_ctx *vk, struct priv *p)
+bool ra_vk_ctx_resize(struct ra_ctx *ctx, int width, int height)
{
- assert(p->old_swapchain);
- vkDestroySwapchainKHR(vk->dev, p->old_swapchain, MPVK_ALLOCATOR);
- p->old_swapchain = NULL;
-}
-
-bool ra_vk_ctx_resize(struct ra_swapchain *sw, int w, int h)
-{
- struct priv *p = sw->priv;
- if (w == p->w && h == p->h)
- return true;
-
- struct ra *ra = sw->ctx->ra;
- struct mpvk_ctx *vk = p->vk;
- VkImage *vkimages = NULL;
-
- // It's invalid to trigger another swapchain recreation while there's
- // more than one swapchain already active, so we need to flush any pending
- // asynchronous swapchain release operations that may be ongoing.
- while (p->old_swapchain)
- mpvk_poll_commands(vk, 100000); // 100μs
-
- VkSwapchainCreateInfoKHR sinfo = p->protoInfo;
- sinfo.imageExtent = (VkExtent2D){ w, h };
- sinfo.oldSwapchain = p->swapchain;
-
- if (!update_swapchain_info(p, &sinfo))
- goto error;
-
- VK(vkCreateSwapchainKHR(vk->dev, &sinfo, MPVK_ALLOCATOR, &p->swapchain));
- p->w = w;
- p->h = h;
-
- // Freeing the old swapchain while it's still in use is an error, so do
- // it asynchronously once the device is idle.
- if (sinfo.oldSwapchain) {
- p->old_swapchain = sinfo.oldSwapchain;
- vk_dev_callback(vk, (vk_cb) destroy_swapchain, vk, p);
- }
-
- // Get the new swapchain images
- int num;
- VK(vkGetSwapchainImagesKHR(vk->dev, p->swapchain, &num, NULL));
- vkimages = talloc_array(NULL, VkImage, num);
- VK(vkGetSwapchainImagesKHR(vk->dev, p->swapchain, &num, vkimages));
-
- // If needed, allocate some more semaphores
- while (num > p->num_sems) {
- VkSemaphore sem_in, sem_out;
- static const VkSemaphoreCreateInfo seminfo = {
- .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
- };
- VK(vkCreateSemaphore(vk->dev, &seminfo, MPVK_ALLOCATOR, &sem_in));
- VK(vkCreateSemaphore(vk->dev, &seminfo, MPVK_ALLOCATOR, &sem_out));
-
- int idx = p->num_sems++;
- MP_TARRAY_GROW(p, p->sems_in, idx);
- MP_TARRAY_GROW(p, p->sems_out, idx);
- p->sems_in[idx] = sem_in;
- p->sems_out[idx] = sem_out;
- }
-
- // Invalidate the queued texture
- p->queued_fbo = (struct ra_fbo) {0};
-
- // Recreate the ra_tex wrappers
- for (int i = 0; i < p->num_images; i++)
- ra_tex_free(ra, &p->images[i]);
-
- p->num_images = num;
- MP_TARRAY_GROW(p, p->images, p->num_images);
- for (int i = 0; i < num; i++) {
- p->images[i] = ra_vk_wrap_swapchain_img(ra, vkimages[i], sinfo);
- if (!p->images[i])
- goto error;
- }
+ struct priv *p = ctx->swapchain->priv;
- talloc_free(vkimages);
- return true;
+ bool ok = pl_swapchain_resize(p->swapchain, &width, &height);
+ ctx->vo->dwidth = width;
+ ctx->vo->dheight = height;
-error:
- talloc_free(vkimages);
- vkDestroySwapchainKHR(vk->dev, p->swapchain, MPVK_ALLOCATOR);
- p->swapchain = NULL;
- return false;
+ return ok;
}
static int color_depth(struct ra_swapchain *sw)
{
- struct priv *p = sw->priv;
- int bits = 0;
-
- if (!p->num_images)
- return bits;
-
- // The channel with the most bits is probably the most authoritative about
- // the actual color information (consider e.g. a2bgr10). Slight downside
- // in that it results in rounding r/b for e.g. rgb565, but we don't pick
- // surfaces with fewer than 8 bits anyway.
- const struct ra_format *fmt = p->images[0]->params.format;
- for (int i = 0; i < fmt->num_components; i++) {
- int depth = fmt->component_depth[i];
- bits = MPMAX(bits, depth ? depth : fmt->component_size[i]);
- }
-
- return bits;
+ return 0; // TODO: implement this somehow?
}
static bool start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
{
struct priv *p = sw->priv;
- struct mpvk_ctx *vk = p->vk;
- if (!p->swapchain)
+ struct pl_swapchain_frame frame;
+ if (!pl_swapchain_start_frame(p->swapchain, &frame))
+ return false;
+ if (!mppl_wrap_tex(sw->ctx->ra, frame.fbo, &p->proxy_tex))
return false;
- if (p->queued_fbo.tex) {
- assert(out_fbo != &p->queued_fbo);
- *out_fbo = p->queued_fbo;
- p->queued_fbo = (struct ra_fbo) {0};
- return true;
- }
-
- VkSemaphore sem_in = p->sems_in[p->idx_sems];
- MP_TRACE(vk, "vkAcquireNextImageKHR signals %p\n", (void *)sem_in);
-
- for (int attempts = 0; attempts < 2; attempts++) {
- uint32_t imgidx = 0;
- VkResult res = vkAcquireNextImageKHR(vk->dev, p->swapchain, UINT64_MAX,
- sem_in, NULL, &imgidx);
-
- switch (res) {
- case VK_SUCCESS:
- p->last_imgidx = imgidx;
- *out_fbo = (struct ra_fbo) {
- .tex = p->images[imgidx],
- .flip = false,
- };
- ra_tex_vk_external_dep(sw->ctx->ra, out_fbo->tex, sem_in);
- return true;
-
- case VK_ERROR_OUT_OF_DATE_KHR: {
- // In these cases try recreating the swapchain
- int w = p->w, h = p->h;
- p->w = p->h = 0; // invalidate the current state
- if (!ra_vk_ctx_resize(sw, w, h))
- return false;
- continue;
- }
-
- default:
- MP_ERR(vk, "Failed acquiring swapchain image: %s\n", vk_err(res));
- return false;
- }
- }
-
- // If we've exhausted the number of attempts to recreate the swapchain,
- // just give up silently.
- return false;
-}
+ *out_fbo = (struct ra_fbo) {
+ .tex = &p->proxy_tex,
+ .flip = frame.flipped,
+ };
-static void present_cb(struct priv *p, void *arg)
-{
- p->frames_in_flight--;
+ return true;
}
static bool submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame)
{
struct priv *p = sw->priv;
- struct ra *ra = sw->ctx->ra;
- struct mpvk_ctx *vk = p->vk;
- if (!p->swapchain)
- return false;
-
- struct vk_cmd *cmd = ra_vk_submit(ra, 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);
-
- p->frames_in_flight++;
- vk_cmd_callback(cmd, (vk_cb) present_cb, p, NULL);
-
- vk_cmd_queue(vk, cmd);
- if (!mpvk_flush_commands(vk))
- return false;
-
- // Submit to the same queue that we were currently rendering to
- struct vk_cmdpool *pool_gfx = vk->pool_graphics;
- VkQueue queue = pool_gfx->queues[pool_gfx->idx_queues];
-
- // 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;
- }
-
- VkPresentInfoKHR pinfo = {
- .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
- .waitSemaphoreCount = 1,
- .pWaitSemaphores = &sem_out,
- .swapchainCount = 1,
- .pSwapchains = &p->swapchain,
- .pImageIndices = &p->last_imgidx,
- };
-
- MP_TRACE(vk, "vkQueuePresentKHR waits on %p\n", (void *)sem_out);
- VkResult res = vkQueuePresentKHR(queue, &pinfo);
- switch (res) {
- case VK_SUCCESS:
- case VK_SUBOPTIMAL_KHR:
- return true;
-
- case VK_ERROR_OUT_OF_DATE_KHR:
- // We can silently ignore this error, since the next start_frame will
- // recreate the swapchain automatically.
- return true;
-
- default:
- MP_ERR(vk, "Failed presenting to queue %p: %s\n", (void *)queue,
- vk_err(res));
- return false;
- }
+ return pl_swapchain_submit_frame(p->swapchain);
}
static void swap_buffers(struct ra_swapchain *sw)
{
struct priv *p = sw->priv;
-
- while (p->frames_in_flight >= sw->ctx->opts.swapchain_depth)
- mpvk_poll_commands(p->vk, 100000); // 100μs
-
- // Also try and block until the next hardware buffer swap early. this
- // prevents start_frame from blocking later, thus slightly improving the
- // frame timing stats. (since mpv assumes most blocking will happen in
- // swap_buffers)
- start_frame(sw, &p->queued_fbo);
+ pl_swapchain_swap_buffers(p->swapchain);
}
static const struct ra_swapchain_fns vulkan_swapchain = {