summaryrefslogtreecommitdiffstats
path: root/video/out/vulkan
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/vulkan')
-rw-r--r--video/out/vulkan/common.h40
-rw-r--r--video/out/vulkan/context.c372
-rw-r--r--video/out/vulkan/context.h31
-rw-r--r--video/out/vulkan/context_android.c96
-rw-r--r--video/out/vulkan/context_display.c491
-rw-r--r--video/out/vulkan/context_mac.m119
-rw-r--r--video/out/vulkan/context_wayland.c167
-rw-r--r--video/out/vulkan/context_win.c106
-rw-r--r--video/out/vulkan/context_xlib.c143
-rw-r--r--video/out/vulkan/utils.c42
-rw-r--r--video/out/vulkan/utils.h6
11 files changed, 1613 insertions, 0 deletions
diff --git a/video/out/vulkan/common.h b/video/out/vulkan/common.h
new file mode 100644
index 0000000..d006942
--- /dev/null
+++ b/video/out/vulkan/common.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#include "config.h"
+
+#include "common/common.h"
+#include "common/msg.h"
+
+// We need to define all platforms we want to support. Since we have
+// our own mechanism for checking this, we re-define the right symbols
+#if HAVE_WAYLAND
+#define VK_USE_PLATFORM_WAYLAND_KHR
+#endif
+#if HAVE_X11
+#define VK_USE_PLATFORM_XLIB_KHR
+#endif
+#if HAVE_WIN32_DESKTOP
+#define VK_USE_PLATFORM_WIN32_KHR
+#endif
+#if HAVE_COCOA
+#define VK_USE_PLATFORM_MACOS_MVK
+#define VK_USE_PLATFORM_METAL_EXT
+#endif
+
+#include <libplacebo/vulkan.h>
+
+// Shared struct used to hold vulkan context information
+struct mpvk_ctx {
+ pl_log pllog;
+ pl_vk_inst vkinst;
+ pl_vulkan vulkan;
+ pl_gpu gpu; // points to vulkan->gpu for convenience
+ pl_swapchain swapchain;
+ VkSurfaceKHR surface;
+};
diff --git a/video/out/vulkan/context.c b/video/out/vulkan/context.c
new file mode 100644
index 0000000..5087403
--- /dev/null
+++ b/video/out/vulkan/context.c
@@ -0,0 +1,372 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#if HAVE_LAVU_UUID
+#include <libavutil/uuid.h>
+#else
+#include "misc/uuid.h"
+#endif
+
+#include "options/m_config.h"
+#include "video/out/placebo/ra_pl.h"
+
+#include "context.h"
+#include "utils.h"
+
+struct vulkan_opts {
+ char *device; // force a specific GPU
+ int swap_mode;
+ int queue_count;
+ bool async_transfer;
+ bool async_compute;
+};
+
+static int vk_validate_dev(struct mp_log *log, const struct m_option *opt,
+ struct bstr name, const char **value)
+{
+ struct bstr param = bstr0(*value);
+ int ret = M_OPT_INVALID;
+ VkResult res;
+
+ // Create a dummy instance to validate/list the devices
+ VkInstanceCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &(VkApplicationInfo) {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .apiVersion = VK_API_VERSION_1_1,
+ }
+ };
+
+ VkInstance inst;
+ VkPhysicalDevice *devices = NULL;
+ uint32_t num = 0;
+
+ res = vkCreateInstance(&info, NULL, &inst);
+ if (res != VK_SUCCESS)
+ goto done;
+
+ res = vkEnumeratePhysicalDevices(inst, &num, NULL);
+ if (res != VK_SUCCESS)
+ goto done;
+
+ devices = talloc_array(NULL, VkPhysicalDevice, num);
+ res = vkEnumeratePhysicalDevices(inst, &num, devices);
+ if (res != VK_SUCCESS)
+ goto done;
+
+ bool help = bstr_equals0(param, "help");
+ if (help) {
+ mp_info(log, "Available vulkan devices:\n");
+ ret = M_OPT_EXIT;
+ }
+
+ AVUUID param_uuid;
+ bool is_uuid = av_uuid_parse(*value, param_uuid) == 0;
+
+ for (int i = 0; i < num; i++) {
+ VkPhysicalDeviceIDPropertiesKHR id_prop = { 0 };
+ id_prop.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES_KHR;
+
+ VkPhysicalDeviceProperties2KHR prop2 = { 0 };
+ prop2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
+ prop2.pNext = &id_prop;
+
+ vkGetPhysicalDeviceProperties2(devices[i], &prop2);
+
+ const VkPhysicalDeviceProperties *prop = &prop2.properties;
+
+ if (help) {
+ char device_uuid[37];
+ av_uuid_unparse(id_prop.deviceUUID, device_uuid);
+ mp_info(log, " '%s' (GPU %d, PCI ID %x:%x, UUID %s)\n",
+ prop->deviceName, i, (unsigned)prop->vendorID,
+ (unsigned)prop->deviceID, device_uuid);
+ } else if (bstr_equals0(param, prop->deviceName)) {
+ ret = 0;
+ goto done;
+ } else if (is_uuid && av_uuid_equal(param_uuid, id_prop.deviceUUID)) {
+ ret = 0;
+ goto done;
+ }
+ }
+
+ if (!help)
+ mp_err(log, "No device with %s '%.*s'!\n", is_uuid ? "UUID" : "name",
+ BSTR_P(param));
+
+done:
+ talloc_free(devices);
+ return ret;
+}
+
+#define OPT_BASE_STRUCT struct vulkan_opts
+const struct m_sub_options vulkan_conf = {
+ .opts = (const struct m_option[]) {
+ {"vulkan-device", OPT_STRING_VALIDATE(device, vk_validate_dev)},
+ {"vulkan-swap-mode", OPT_CHOICE(swap_mode,
+ {"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})},
+ {"vulkan-queue-count", OPT_INT(queue_count), M_RANGE(1, 8)},
+ {"vulkan-async-transfer", OPT_BOOL(async_transfer)},
+ {"vulkan-async-compute", OPT_BOOL(async_compute)},
+ {"vulkan-disable-events", OPT_REMOVED("Unused")},
+ {0}
+ },
+ .size = sizeof(struct vulkan_opts),
+ .defaults = &(struct vulkan_opts) {
+ .swap_mode = -1,
+ .queue_count = 1,
+ .async_transfer = true,
+ .async_compute = true,
+ },
+};
+
+struct priv {
+ struct mpvk_ctx *vk;
+ struct vulkan_opts *opts;
+ struct ra_vk_ctx_params params;
+ struct ra_tex proxy_tex;
+};
+
+static const struct ra_swapchain_fns vulkan_swapchain;
+
+struct mpvk_ctx *ra_vk_ctx_get(struct ra_ctx *ctx)
+{
+ if (!ctx->swapchain || ctx->swapchain->fns != &vulkan_swapchain)
+ return NULL;
+
+ struct priv *p = ctx->swapchain->priv;
+ return p->vk;
+}
+
+void ra_vk_ctx_uninit(struct ra_ctx *ctx)
+{
+ if (!ctx->swapchain)
+ return;
+
+ struct priv *p = ctx->swapchain->priv;
+ struct mpvk_ctx *vk = p->vk;
+
+ if (ctx->ra) {
+ pl_gpu_finish(vk->gpu);
+ pl_swapchain_destroy(&vk->swapchain);
+ ctx->ra->fns->destroy(ctx->ra);
+ ctx->ra = NULL;
+ }
+
+ vk->gpu = NULL;
+ pl_vulkan_destroy(&vk->vulkan);
+ TA_FREEP(&ctx->swapchain);
+}
+
+bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
+ struct ra_vk_ctx_params params,
+ VkPresentModeKHR preferred_mode)
+{
+ struct ra_swapchain *sw = ctx->swapchain = talloc_zero(NULL, struct ra_swapchain);
+ sw->ctx = ctx;
+ sw->fns = &vulkan_swapchain;
+
+ struct priv *p = sw->priv = talloc_zero(sw, struct priv);
+ p->vk = vk;
+ p->params = params;
+ p->opts = mp_get_config_group(p, ctx->global, &vulkan_conf);
+
+ VkPhysicalDeviceFeatures2 features = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
+ };
+
+#if HAVE_VULKAN_INTEROP
+ /*
+ * Request the additional extensions and features required to make full use
+ * of the ffmpeg Vulkan hwcontext and video decoding capability.
+ */
+ const char *opt_extensions[] = {
+ VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME,
+ VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME,
+ VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME,
+ VK_KHR_VIDEO_DECODE_H264_EXTENSION_NAME,
+ VK_KHR_VIDEO_DECODE_H265_EXTENSION_NAME,
+ VK_KHR_VIDEO_QUEUE_EXTENSION_NAME,
+ // This is a literal string as it's not in the official headers yet.
+ "VK_MESA_video_decode_av1",
+ };
+
+ VkPhysicalDeviceDescriptorBufferFeaturesEXT descriptor_buffer_feature = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT,
+ .pNext = NULL,
+ .descriptorBuffer = true,
+ .descriptorBufferPushDescriptors = true,
+ };
+
+ VkPhysicalDeviceShaderAtomicFloatFeaturesEXT atomic_float_feature = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_FEATURES_EXT,
+ .pNext = &descriptor_buffer_feature,
+ .shaderBufferFloat32Atomics = true,
+ .shaderBufferFloat32AtomicAdd = true,
+ };
+
+ features.pNext = &atomic_float_feature;
+#endif
+
+ AVUUID param_uuid = { 0 };
+ bool is_uuid = p->opts->device &&
+ av_uuid_parse(p->opts->device, param_uuid) == 0;
+
+ assert(vk->pllog);
+ assert(vk->vkinst);
+ struct pl_vulkan_params device_params = {
+ .instance = vk->vkinst->instance,
+ .get_proc_addr = vk->vkinst->get_proc_addr,
+ .surface = vk->surface,
+ .async_transfer = p->opts->async_transfer,
+ .async_compute = p->opts->async_compute,
+ .queue_count = p->opts->queue_count,
+#if HAVE_VULKAN_INTEROP
+ .extra_queues = VK_QUEUE_VIDEO_DECODE_BIT_KHR,
+ .opt_extensions = opt_extensions,
+ .num_opt_extensions = MP_ARRAY_SIZE(opt_extensions),
+#endif
+ .features = &features,
+ .device_name = is_uuid ? NULL : p->opts->device,
+ };
+ if (is_uuid)
+ av_uuid_copy(device_params.device_uuid, param_uuid);
+
+ vk->vulkan = pl_vulkan_create(vk->pllog, &device_params);
+ if (!vk->vulkan)
+ goto error;
+
+ vk->gpu = vk->vulkan->gpu;
+ ctx->ra = ra_create_pl(vk->gpu, ctx->log);
+ if (!ctx->ra)
+ goto error;
+
+ // Create the swapchain
+ struct pl_vulkan_swapchain_params pl_params = {
+ .surface = vk->surface,
+ .present_mode = preferred_mode,
+ .swapchain_depth = ctx->vo->opts->swapchain_depth,
+ // mpv already handles resize events, so gracefully allow suboptimal
+ // swapchains to exist in order to make resizing even smoother
+ .allow_suboptimal = true,
+ };
+
+ if (p->opts->swap_mode >= 0) // user override
+ pl_params.present_mode = p->opts->swap_mode;
+
+ vk->swapchain = pl_vulkan_create_swapchain(vk->vulkan, &pl_params);
+ if (!vk->swapchain)
+ goto error;
+
+ return true;
+
+error:
+ ra_vk_ctx_uninit(ctx);
+ return false;
+}
+
+bool ra_vk_ctx_resize(struct ra_ctx *ctx, int width, int height)
+{
+ struct priv *p = ctx->swapchain->priv;
+
+ bool ok = pl_swapchain_resize(p->vk->swapchain, &width, &height);
+ ctx->vo->dwidth = width;
+ ctx->vo->dheight = height;
+
+ return ok;
+}
+
+char *ra_vk_ctx_get_device_name(struct ra_ctx *ctx)
+{
+ /*
+ * This implementation is a bit odd because it has to work even if the
+ * ctx hasn't been initialised yet. A context implementation may need access
+ * to the device name before it can fully initialise the ctx.
+ */
+ struct vulkan_opts *opts = mp_get_config_group(NULL, ctx->global,
+ &vulkan_conf);
+ char *device_name = talloc_strdup(NULL, opts->device);
+ talloc_free(opts);
+ return device_name;
+}
+
+static int color_depth(struct ra_swapchain *sw)
+{
+ 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 pl_swapchain_frame frame;
+
+ bool visible = true;
+ if (p->params.check_visible)
+ visible = p->params.check_visible(sw->ctx);
+
+ // If out_fbo is NULL, this was called from vo_gpu_next. Bail out.
+ if (out_fbo == NULL || !visible)
+ return visible;
+
+ if (!pl_swapchain_start_frame(p->vk->swapchain, &frame))
+ return false;
+ if (!mppl_wrap_tex(sw->ctx->ra, frame.fbo, &p->proxy_tex))
+ return false;
+
+ *out_fbo = (struct ra_fbo) {
+ .tex = &p->proxy_tex,
+ .flip = frame.flipped,
+ };
+
+ return true;
+}
+
+static bool submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame)
+{
+ struct priv *p = sw->priv;
+ return pl_swapchain_submit_frame(p->vk->swapchain);
+}
+
+static void swap_buffers(struct ra_swapchain *sw)
+{
+ struct priv *p = sw->priv;
+ pl_swapchain_swap_buffers(p->vk->swapchain);
+ if (p->params.swap_buffers)
+ p->params.swap_buffers(sw->ctx);
+}
+
+static void get_vsync(struct ra_swapchain *sw,
+ struct vo_vsync_info *info)
+{
+ struct priv *p = sw->priv;
+ if (p->params.get_vsync)
+ p->params.get_vsync(sw->ctx, info);
+}
+
+static const struct ra_swapchain_fns vulkan_swapchain = {
+ .color_depth = color_depth,
+ .start_frame = start_frame,
+ .submit_frame = submit_frame,
+ .swap_buffers = swap_buffers,
+ .get_vsync = get_vsync,
+};
diff --git a/video/out/vulkan/context.h b/video/out/vulkan/context.h
new file mode 100644
index 0000000..c846942
--- /dev/null
+++ b/video/out/vulkan/context.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "video/out/gpu/context.h"
+#include "common.h"
+
+struct ra_vk_ctx_params {
+ // See ra_swapchain_fns.get_vsync.
+ void (*get_vsync)(struct ra_ctx *ctx, struct vo_vsync_info *info);
+
+ // For special contexts (i.e. wayland) that want to check visibility
+ // before drawing a frame.
+ bool (*check_visible)(struct ra_ctx *ctx);
+
+ // In case something special needs to be done on the buffer swap.
+ void (*swap_buffers)(struct ra_ctx *ctx);
+};
+
+// Helpers for ra_ctx based on ra_vk. These initialize ctx->ra and ctx->swchain.
+void ra_vk_ctx_uninit(struct ra_ctx *ctx);
+bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
+ struct ra_vk_ctx_params params,
+ VkPresentModeKHR preferred_mode);
+
+// Handles a resize request, and updates ctx->vo->dwidth/dheight
+bool ra_vk_ctx_resize(struct ra_ctx *ctx, int width, int height);
+
+// May be called on a ra_ctx of any type.
+struct mpvk_ctx *ra_vk_ctx_get(struct ra_ctx *ctx);
+
+// Get the user requested Vulkan device name.
+char *ra_vk_ctx_get_device_name(struct ra_ctx *ctx);
diff --git a/video/out/vulkan/context_android.c b/video/out/vulkan/context_android.c
new file mode 100644
index 0000000..ddab391
--- /dev/null
+++ b/video/out/vulkan/context_android.c
@@ -0,0 +1,96 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_android.h>
+
+#include "video/out/android_common.h"
+#include "common.h"
+#include "context.h"
+#include "utils.h"
+
+struct priv {
+ struct mpvk_ctx vk;
+};
+
+static void android_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+
+ vo_android_uninit(ctx->vo);
+}
+
+static bool android_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+
+ if (!vo_android_init(ctx->vo))
+ goto fail;
+
+ if (!mpvk_init(vk, ctx, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME))
+ goto fail;
+
+ VkAndroidSurfaceCreateInfoKHR info = {
+ .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
+ .window = vo_android_native_window(ctx->vo)
+ };
+
+ struct ra_vk_ctx_params params = {0};
+
+ VkInstance inst = vk->vkinst->instance;
+ VkResult res = vkCreateAndroidSurfaceKHR(inst, &info, NULL, &vk->surface);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating Android surface\n");
+ goto fail;
+ }
+
+ if (!ra_vk_ctx_init(ctx, vk, params, VK_PRESENT_MODE_FIFO_KHR))
+ goto fail;
+
+ return true;
+fail:
+ android_uninit(ctx);
+ return false;
+}
+
+static bool android_reconfig(struct ra_ctx *ctx)
+{
+ int w, h;
+ if (!vo_android_surface_size(ctx->vo, &w, &h))
+ return false;
+
+ ra_vk_ctx_resize(ctx, w, h);
+ return true;
+}
+
+static int android_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ return VO_NOTIMPL;
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_android = {
+ .type = "vulkan",
+ .name = "androidvk",
+ .reconfig = android_reconfig,
+ .control = android_control,
+ .init = android_init,
+ .uninit = android_uninit,
+};
diff --git a/video/out/vulkan/context_display.c b/video/out/vulkan/context_display.c
new file mode 100644
index 0000000..84cef1e
--- /dev/null
+++ b/video/out/vulkan/context_display.c
@@ -0,0 +1,491 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "context.h"
+#include "options/m_config.h"
+#include "utils.h"
+
+#if HAVE_DRM
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "libmpv/render_gl.h"
+#include "video/out/drm_common.h"
+#endif
+
+struct vulkan_display_opts {
+ int display;
+ int mode;
+ int plane;
+};
+
+struct mode_selector {
+ // Indexes of selected display/mode/plane.
+ int display_idx;
+ int mode_idx;
+ int plane_idx;
+
+ // Must be freed with talloc_free
+ VkDisplayModePropertiesKHR *out_mode_props;
+};
+
+/**
+ * If a selector is passed, verify that it is valid and return the matching
+ * mode properties. If null is passed, walk all modes and print them out.
+ */
+static bool walk_display_properties(struct mp_log *log,
+ int msgl_err,
+ VkPhysicalDevice device,
+ struct mode_selector *selector) {
+ bool ret = false;
+ VkResult res;
+
+ int msgl_info = selector ? MSGL_TRACE : MSGL_INFO;
+
+ // Use a dummy as parent for all other allocations.
+ void *tmp = talloc_new(NULL);
+
+ VkPhysicalDeviceProperties prop;
+ vkGetPhysicalDeviceProperties(device, &prop);
+ mp_msg(log, msgl_info, " '%s' (GPU ID %x:%x)\n", prop.deviceName,
+ (unsigned)prop.vendorID, (unsigned)prop.deviceID);
+
+ // Count displays. This must be done before enumerating planes with the
+ // Intel driver, or it will not enumerate any planes. WTF.
+ int num_displays = 0;
+ vkGetPhysicalDeviceDisplayPropertiesKHR(device, &num_displays, NULL);
+ if (!num_displays) {
+ mp_msg(log, msgl_info, " No available displays for device.\n");
+ goto done;
+ }
+ if (selector && selector->display_idx + 1 > num_displays) {
+ mp_msg(log, msgl_err, "Selected display (%d) not present.\n",
+ selector->display_idx);
+ goto done;
+ }
+
+ // Enumerate Planes
+ int num_planes = 0;
+ vkGetPhysicalDeviceDisplayPlanePropertiesKHR(device, &num_planes, NULL);
+ if (!num_planes) {
+ mp_msg(log, msgl_info, " No available planes for device.\n");
+ goto done;
+ }
+ if (selector && selector->plane_idx + 1 > num_planes) {
+ mp_msg(log, msgl_err, "Selected plane (%d) not present.\n",
+ selector->plane_idx);
+ goto done;
+ }
+
+ VkDisplayPlanePropertiesKHR *planes =
+ talloc_array(tmp, VkDisplayPlanePropertiesKHR, num_planes);
+ res = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(device, &num_planes,
+ planes);
+ if (res != VK_SUCCESS) {
+ mp_msg(log, msgl_err, " Failed enumerating planes\n");
+ goto done;
+ }
+
+ // Allocate zeroed arrays so that planes with no displays have a null entry.
+ VkDisplayKHR **planes_to_displays =
+ talloc_zero_array(tmp, VkDisplayKHR *, num_planes);
+ for (int j = 0; j < num_planes; j++) {
+ int num_displays_for_plane = 0;
+ vkGetDisplayPlaneSupportedDisplaysKHR(device, j,
+ &num_displays_for_plane, NULL);
+ if (!num_displays_for_plane)
+ continue;
+
+ // Null terminated array
+ VkDisplayKHR *displays =
+ talloc_zero_array(planes_to_displays, VkDisplayKHR,
+ num_displays_for_plane + 1);
+ res = vkGetDisplayPlaneSupportedDisplaysKHR(device, j,
+ &num_displays_for_plane,
+ displays);
+ if (res != VK_SUCCESS) {
+ mp_msg(log, msgl_err, " Failed enumerating plane displays\n");
+ continue;
+ }
+ planes_to_displays[j] = displays;
+ }
+
+ // Enumerate Displays and Modes
+ VkDisplayPropertiesKHR *props =
+ talloc_array(tmp, VkDisplayPropertiesKHR, num_displays);
+ res = vkGetPhysicalDeviceDisplayPropertiesKHR(device, &num_displays, props);
+ if (res != VK_SUCCESS) {
+ mp_msg(log, msgl_err, " Failed enumerating display properties\n");
+ goto done;
+ }
+
+ for (int j = 0; j < num_displays; j++) {
+ if (selector && selector->display_idx != j)
+ continue;
+
+ mp_msg(log, msgl_info, " Display %d: '%s' (%dx%d)\n",
+ j,
+ props[j].displayName,
+ props[j].physicalResolution.width,
+ props[j].physicalResolution.height);
+
+ VkDisplayKHR display = props[j].display;
+
+ mp_msg(log, msgl_info, " Modes:\n");
+
+ int num_modes = 0;
+ vkGetDisplayModePropertiesKHR(device, display, &num_modes, NULL);
+ if (!num_modes) {
+ mp_msg(log, msgl_info, " No available modes for display.\n");
+ continue;
+ }
+ if (selector && selector->mode_idx + 1 > num_modes) {
+ mp_msg(log, msgl_err, "Selected mode (%d) not present.\n",
+ selector->mode_idx);
+ goto done;
+ }
+
+ VkDisplayModePropertiesKHR *modes =
+ talloc_array(tmp, VkDisplayModePropertiesKHR, num_modes);
+ res = vkGetDisplayModePropertiesKHR(device, display, &num_modes, modes);
+ if (res != VK_SUCCESS) {
+ mp_msg(log, msgl_err, " Failed enumerating display modes\n");
+ continue;
+ }
+
+ for (int k = 0; k < num_modes; k++) {
+ if (selector && selector->mode_idx != k)
+ continue;
+
+ mp_msg(log, msgl_info, " Mode %02d: %dx%d (%02d.%03d Hz)\n", k,
+ modes[k].parameters.visibleRegion.width,
+ modes[k].parameters.visibleRegion.height,
+ modes[k].parameters.refreshRate / 1000,
+ modes[k].parameters.refreshRate % 1000);
+
+ if (selector)
+ selector->out_mode_props = talloc_dup(NULL, &modes[k]);
+ }
+
+ int found_plane = -1;
+ mp_msg(log, msgl_info, " Planes:\n");
+ for (int k = 0; k < num_planes; k++) {
+ VkDisplayKHR *displays = planes_to_displays[k];
+ if (!displays) {
+ // This plane is not connected to any displays.
+ continue;
+ }
+ for (int d = 0; displays[d]; d++) {
+ if (displays[d] == display) {
+ if (selector && selector->plane_idx != k)
+ continue;
+
+ mp_msg(log, msgl_info, " Plane: %d\n", k);
+ found_plane = k;
+ }
+ }
+ }
+ if (selector && selector->plane_idx != found_plane) {
+ mp_msg(log, msgl_err,
+ "Selected plane (%d) not available on selected display.\n",
+ selector->plane_idx);
+ goto done;
+ }
+ }
+ ret = true;
+done:
+ talloc_free(tmp);
+ return ret;
+}
+
+static int print_display_info(struct mp_log *log, const struct m_option *opt,
+ struct bstr name) {
+ VkResult res;
+ VkPhysicalDevice *devices = NULL;
+
+ // Create a dummy instance to list the resources
+ VkInstanceCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .enabledExtensionCount = 1,
+ .ppEnabledExtensionNames = (const char*[]) {
+ VK_KHR_DISPLAY_EXTENSION_NAME
+ },
+ };
+
+ VkInstance inst = NULL;
+ res = vkCreateInstance(&info, NULL, &inst);
+ if (res != VK_SUCCESS) {
+ mp_warn(log, "Unable to create Vulkan instance.\n");
+ goto done;
+ }
+
+ uint32_t num_devices = 0;
+ vkEnumeratePhysicalDevices(inst, &num_devices, NULL);
+ if (!num_devices) {
+ mp_info(log, "No Vulkan devices detected.\n");
+ goto done;
+ }
+
+ devices = talloc_array(NULL, VkPhysicalDevice, num_devices);
+ vkEnumeratePhysicalDevices(inst, &num_devices, devices);
+ if (res != VK_SUCCESS) {
+ mp_warn(log, "Failed enumerating physical devices.\n");
+ goto done;
+ }
+
+ mp_info(log, "Vulkan Devices:\n");
+ for (int i = 0; i < num_devices; i++) {
+ walk_display_properties(log, MSGL_WARN, devices[i], NULL);
+ }
+
+done:
+ talloc_free(devices);
+ vkDestroyInstance(inst, NULL);
+ return M_OPT_EXIT;
+}
+
+#define OPT_BASE_STRUCT struct vulkan_display_opts
+const struct m_sub_options vulkan_display_conf = {
+ .opts = (const struct m_option[]) {
+ {"vulkan-display-display", OPT_INT(display),
+ .help = print_display_info,
+ },
+ {"vulkan-display-mode", OPT_INT(mode),
+ .help = print_display_info,
+ },
+ {"vulkan-display-plane", OPT_INT(plane),
+ .help = print_display_info,
+ },
+ {0}
+ },
+ .size = sizeof(struct vulkan_display_opts),
+ .defaults = &(struct vulkan_display_opts) {0},
+};
+
+struct priv {
+ struct mpvk_ctx vk;
+ struct vulkan_display_opts *opts;
+ uint32_t width;
+ uint32_t height;
+
+#if HAVE_DRM
+ struct mpv_opengl_drm_params_v2 drm_params;
+#endif
+};
+
+#if HAVE_DRM
+static void open_render_fd(struct ra_ctx *ctx, const char *render_path)
+{
+ struct priv *p = ctx->priv;
+ p->drm_params.fd = -1;
+ p->drm_params.render_fd = open(render_path, O_RDWR | O_CLOEXEC);
+ if (p->drm_params.render_fd == -1) {
+ MP_WARN(ctx, "Failed to open render node: %s\n",
+ strerror(errno));
+ }
+}
+
+static bool drm_setup(struct ra_ctx *ctx, int display_idx,
+ VkPhysicalDevicePCIBusInfoPropertiesEXT *pci_props)
+{
+ drmDevice *devs[32] = {};
+ int count = drmGetDevices2(0, devs, MP_ARRAY_SIZE(devs));
+ for (int i = 0; i < count; i++) {
+ drmDevice *dev = devs[i];
+
+ if (dev->bustype != DRM_BUS_PCI ||
+ dev->businfo.pci->domain != pci_props->pciDomain ||
+ dev->businfo.pci->bus != pci_props->pciBus ||
+ dev->businfo.pci->dev != pci_props->pciDevice ||
+ dev->businfo.pci->func != pci_props->pciFunction)
+ {
+ continue;
+ }
+
+ // Found our matching device.
+ MP_DBG(ctx, "DRM device found for Vulkan device at %04X:%02X:%02X:%02X\n",
+ pci_props->pciDomain, pci_props->pciBus,
+ pci_props->pciDevice, pci_props->pciFunction);
+
+ if (!(dev->available_nodes & 1 << DRM_NODE_RENDER)) {
+ MP_DBG(ctx, "Card does not have a render node.\n");
+ continue;
+ }
+
+ open_render_fd(ctx, dev->nodes[DRM_NODE_RENDER]);
+
+ break;
+ }
+ drmFreeDevices(devs, MP_ARRAY_SIZE(devs));
+
+ struct priv *p = ctx->priv;
+ if (p->drm_params.render_fd == -1) {
+ MP_WARN(ctx, "Couldn't open DRM render node for Vulkan device "
+ "at: %04X:%02X:%02X:%02X\n",
+ pci_props->pciDomain, pci_props->pciBus,
+ pci_props->pciDevice, pci_props->pciFunction);
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+static void display_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+
+#if HAVE_DRM
+ if (p->drm_params.render_fd != -1) {
+ close(p->drm_params.render_fd);
+ p->drm_params.render_fd = -1;
+ }
+#endif
+}
+
+static bool display_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+ VkResult res;
+ bool ret = false;
+
+ VkDisplayModePropertiesKHR *mode = NULL;
+
+ p->opts = mp_get_config_group(p, ctx->global, &vulkan_display_conf);
+ int display_idx = p->opts->display;
+ int mode_idx = p->opts->mode;
+ int plane_idx = p->opts->plane;
+
+ if (!mpvk_init(vk, ctx, VK_KHR_DISPLAY_EXTENSION_NAME))
+ goto error;
+
+ char *device_name = ra_vk_ctx_get_device_name(ctx);
+ struct pl_vulkan_device_params vulkan_params = {
+ .instance = vk->vkinst->instance,
+ .device_name = device_name,
+ };
+ VkPhysicalDevice device = pl_vulkan_choose_device(vk->pllog, &vulkan_params);
+ talloc_free(device_name);
+ if (!device) {
+ MP_MSG(ctx, msgl, "Failed to open physical device.\n");
+ goto error;
+ }
+
+#if HAVE_DRM
+ VkPhysicalDevicePCIBusInfoPropertiesEXT pci_props = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PCI_BUS_INFO_PROPERTIES_EXT,
+ };
+ VkPhysicalDeviceProperties2KHR props = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
+ .pNext = &pci_props,
+ };
+ vkGetPhysicalDeviceProperties2(device, &props);
+
+ if (!drm_setup(ctx, display_idx, &pci_props))
+ MP_WARN(ctx, "Failed to set up DRM.\n");
+#endif
+
+ struct mode_selector selector = {
+ .display_idx = display_idx,
+ .mode_idx = mode_idx,
+ .plane_idx = plane_idx,
+
+ };
+ if (!walk_display_properties(ctx->log, msgl, device, &selector))
+ goto error;
+ mode = selector.out_mode_props;
+
+ VkDisplaySurfaceCreateInfoKHR xinfo = {
+ .sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR,
+ .displayMode = mode->displayMode,
+ .imageExtent = mode->parameters.visibleRegion,
+ .planeIndex = plane_idx,
+ .transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
+ .alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR,
+ };
+
+ res = vkCreateDisplayPlaneSurfaceKHR(vk->vkinst->instance, &xinfo, NULL,
+ &vk->surface);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating Display surface\n");
+ goto error;
+ }
+
+ p->width = mode->parameters.visibleRegion.width;
+ p->height = mode->parameters.visibleRegion.height;
+
+ struct ra_vk_ctx_params params = {0};
+ if (!ra_vk_ctx_init(ctx, vk, params, VK_PRESENT_MODE_FIFO_KHR))
+ goto error;
+
+#if HAVE_DRM
+ if (p->drm_params.render_fd > -1) {
+ ra_add_native_resource(ctx->ra, "drm_params_v2", &p->drm_params);
+ } else {
+ MP_WARN(ctx,
+ "No DRM render fd available. VAAPI hwaccel will not be usable.\n");
+ }
+#endif
+
+ ret = true;
+
+done:
+ talloc_free(mode);
+ return ret;
+
+error:
+ display_uninit(ctx);
+ goto done;
+}
+
+static bool display_reconfig(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ return ra_vk_ctx_resize(ctx, p->width, p->height);
+}
+
+static int display_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ return VO_NOTIMPL;
+}
+
+static void display_wakeup(struct ra_ctx *ctx)
+{
+ // TODO
+}
+
+static void display_wait_events(struct ra_ctx *ctx, int64_t until_time_ns)
+{
+ // TODO
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_display = {
+ .type = "vulkan",
+ .name = "displayvk",
+ .reconfig = display_reconfig,
+ .control = display_control,
+ .wakeup = display_wakeup,
+ .wait_events = display_wait_events,
+ .init = display_init,
+ .uninit = display_uninit,
+};
diff --git a/video/out/vulkan/context_mac.m b/video/out/vulkan/context_mac.m
new file mode 100644
index 0000000..8ac6e16
--- /dev/null
+++ b/video/out/vulkan/context_mac.m
@@ -0,0 +1,119 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "video/out/gpu/context.h"
+#include "osdep/macOS_swift.h"
+
+#include "common.h"
+#include "context.h"
+#include "utils.h"
+
+struct priv {
+ struct mpvk_ctx vk;
+ MacCommon *vo_mac;
+};
+
+static void mac_vk_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+ [p->vo_mac uninit:ctx->vo];
+}
+
+static void mac_vk_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ [p->vo_mac swapBuffer];
+}
+
+static bool mac_vk_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+
+ if (!mpvk_init(vk, ctx, VK_EXT_METAL_SURFACE_EXTENSION_NAME))
+ goto error;
+
+ p->vo_mac = [[MacCommon alloc] init:ctx->vo];
+ if (!p->vo_mac)
+ goto error;
+
+ VkMetalSurfaceCreateInfoEXT mac_info = {
+ .sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK,
+ .pNext = NULL,
+ .flags = 0,
+ .pLayer = p->vo_mac.layer,
+ };
+
+ struct ra_vk_ctx_params params = {
+ .swap_buffers = mac_vk_swap_buffers,
+ };
+
+ VkInstance inst = vk->vkinst->instance;
+ VkResult res = vkCreateMetalSurfaceEXT(inst, &mac_info, NULL, &vk->surface);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating metal surface\n");
+ goto error;
+ }
+
+ if (!ra_vk_ctx_init(ctx, vk, params, VK_PRESENT_MODE_FIFO_KHR))
+ goto error;
+
+ return true;
+error:
+ if (p->vo_mac)
+ [p->vo_mac uninit:ctx->vo];
+ return false;
+}
+
+static bool resize(struct ra_ctx *ctx)
+{
+ return ra_vk_ctx_resize(ctx, ctx->vo->dwidth, ctx->vo->dheight);
+}
+
+static bool mac_vk_reconfig(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ if (![p->vo_mac config:ctx->vo])
+ return false;
+ return true;
+}
+
+static int mac_vk_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ struct priv *p = ctx->priv;
+ int ret = [p->vo_mac control:ctx->vo events:events request:request data:arg];
+
+ if (*events & VO_EVENT_RESIZE) {
+ if (!resize(ctx))
+ return VO_ERROR;
+ }
+
+ return ret;
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_mac = {
+ .type = "vulkan",
+ .name = "macvk",
+ .reconfig = mac_vk_reconfig,
+ .control = mac_vk_control,
+ .init = mac_vk_init,
+ .uninit = mac_vk_uninit,
+};
diff --git a/video/out/vulkan/context_wayland.c b/video/out/vulkan/context_wayland.c
new file mode 100644
index 0000000..761ff5b
--- /dev/null
+++ b/video/out/vulkan/context_wayland.c
@@ -0,0 +1,167 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "video/out/gpu/context.h"
+#include "video/out/present_sync.h"
+#include "video/out/wayland_common.h"
+
+#include "common.h"
+#include "context.h"
+#include "utils.h"
+
+struct priv {
+ struct mpvk_ctx vk;
+};
+
+static bool wayland_vk_check_visible(struct ra_ctx *ctx)
+{
+ return vo_wayland_check_visible(ctx->vo);
+}
+
+static void wayland_vk_swap_buffers(struct ra_ctx *ctx)
+{
+ struct vo_wayland_state *wl = ctx->vo->wl;
+
+ if (!wl->opts->disable_vsync)
+ vo_wayland_wait_frame(wl);
+
+ if (wl->use_present)
+ present_sync_swap(wl->present);
+}
+
+static void wayland_vk_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
+{
+ struct vo_wayland_state *wl = ctx->vo->wl;
+ if (wl->use_present)
+ present_sync_get_info(wl->present, info);
+}
+
+static void wayland_vk_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+ vo_wayland_uninit(ctx->vo);
+}
+
+static bool wayland_vk_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+
+ if (!mpvk_init(vk, ctx, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME))
+ goto error;
+
+ if (!vo_wayland_init(ctx->vo))
+ goto error;
+
+ VkWaylandSurfaceCreateInfoKHR wlinfo = {
+ .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
+ .display = ctx->vo->wl->display,
+ .surface = ctx->vo->wl->surface,
+ };
+
+ struct ra_vk_ctx_params params = {
+ .check_visible = wayland_vk_check_visible,
+ .swap_buffers = wayland_vk_swap_buffers,
+ .get_vsync = wayland_vk_get_vsync,
+ };
+
+ VkInstance inst = vk->vkinst->instance;
+ VkResult res = vkCreateWaylandSurfaceKHR(inst, &wlinfo, NULL, &vk->surface);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating Wayland surface\n");
+ goto error;
+ }
+
+ /* Because in Wayland clients render whenever they receive a callback from
+ * the compositor, and the fact that the compositor usually stops sending
+ * callbacks once the surface is no longer visible, using FIFO here would
+ * mean the entire player would block on acquiring swapchain images. Hence,
+ * use MAILBOX to guarantee that there'll always be a swapchain image and
+ * the player won't block waiting on those */
+ if (!ra_vk_ctx_init(ctx, vk, params, VK_PRESENT_MODE_MAILBOX_KHR))
+ goto error;
+
+ ra_add_native_resource(ctx->ra, "wl", ctx->vo->wl->display);
+
+ return true;
+
+error:
+ wayland_vk_uninit(ctx);
+ return false;
+}
+
+static bool resize(struct ra_ctx *ctx)
+{
+ struct vo_wayland_state *wl = ctx->vo->wl;
+
+ MP_VERBOSE(wl, "Handling resize on the vk side\n");
+
+ const int32_t width = mp_rect_w(wl->geometry);
+ const int32_t height = mp_rect_h(wl->geometry);
+
+ vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha);
+ vo_wayland_handle_fractional_scale(wl);
+ return ra_vk_ctx_resize(ctx, width, height);
+}
+
+static bool wayland_vk_reconfig(struct ra_ctx *ctx)
+{
+ return vo_wayland_reconfig(ctx->vo);
+}
+
+static int wayland_vk_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ int ret = vo_wayland_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE) {
+ if (!resize(ctx))
+ return VO_ERROR;
+ }
+ return ret;
+}
+
+static void wayland_vk_wakeup(struct ra_ctx *ctx)
+{
+ vo_wayland_wakeup(ctx->vo);
+}
+
+static void wayland_vk_wait_events(struct ra_ctx *ctx, int64_t until_time_ns)
+{
+ vo_wayland_wait_events(ctx->vo, until_time_ns);
+}
+
+static void wayland_vk_update_render_opts(struct ra_ctx *ctx)
+{
+ struct vo_wayland_state *wl = ctx->vo->wl;
+ vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha);
+ wl_surface_commit(wl->surface);
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_wayland = {
+ .type = "vulkan",
+ .name = "waylandvk",
+ .reconfig = wayland_vk_reconfig,
+ .control = wayland_vk_control,
+ .wakeup = wayland_vk_wakeup,
+ .wait_events = wayland_vk_wait_events,
+ .update_render_opts = wayland_vk_update_render_opts,
+ .init = wayland_vk_init,
+ .uninit = wayland_vk_uninit,
+};
diff --git a/video/out/vulkan/context_win.c b/video/out/vulkan/context_win.c
new file mode 100644
index 0000000..a89c644
--- /dev/null
+++ b/video/out/vulkan/context_win.c
@@ -0,0 +1,106 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "video/out/gpu/context.h"
+#include "video/out/w32_common.h"
+
+#include "common.h"
+#include "context.h"
+#include "utils.h"
+
+EXTERN_C IMAGE_DOS_HEADER __ImageBase;
+#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
+
+struct priv {
+ struct mpvk_ctx vk;
+};
+
+static void win_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+ vo_w32_uninit(ctx->vo);
+}
+
+static bool win_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+
+ if (!mpvk_init(vk, ctx, VK_KHR_WIN32_SURFACE_EXTENSION_NAME))
+ goto error;
+
+ if (!vo_w32_init(ctx->vo))
+ goto error;
+
+ VkWin32SurfaceCreateInfoKHR wininfo = {
+ .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
+ .hinstance = HINST_THISCOMPONENT,
+ .hwnd = vo_w32_hwnd(ctx->vo),
+ };
+
+ struct ra_vk_ctx_params params = {0};
+
+ VkInstance inst = vk->vkinst->instance;
+ VkResult res = vkCreateWin32SurfaceKHR(inst, &wininfo, NULL, &vk->surface);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating Windows surface\n");
+ goto error;
+ }
+
+ if (!ra_vk_ctx_init(ctx, vk, params, VK_PRESENT_MODE_FIFO_KHR))
+ goto error;
+
+ return true;
+
+error:
+ win_uninit(ctx);
+ return false;
+}
+
+static bool resize(struct ra_ctx *ctx)
+{
+ return ra_vk_ctx_resize(ctx, ctx->vo->dwidth, ctx->vo->dheight);
+}
+
+static bool win_reconfig(struct ra_ctx *ctx)
+{
+ vo_w32_config(ctx->vo);
+ return resize(ctx);
+}
+
+static int win_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ int ret = vo_w32_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE) {
+ if (!resize(ctx))
+ return VO_ERROR;
+ }
+ return ret;
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_win = {
+ .type = "vulkan",
+ .name = "winvk",
+ .reconfig = win_reconfig,
+ .control = win_control,
+ .init = win_init,
+ .uninit = win_uninit,
+};
diff --git a/video/out/vulkan/context_xlib.c b/video/out/vulkan/context_xlib.c
new file mode 100644
index 0000000..673dc31
--- /dev/null
+++ b/video/out/vulkan/context_xlib.c
@@ -0,0 +1,143 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "video/out/gpu/context.h"
+#include "video/out/present_sync.h"
+#include "video/out/x11_common.h"
+
+#include "common.h"
+#include "context.h"
+#include "utils.h"
+
+struct priv {
+ struct mpvk_ctx vk;
+};
+
+static bool xlib_check_visible(struct ra_ctx *ctx)
+{
+ return vo_x11_check_visible(ctx->vo);
+}
+
+static void xlib_vk_swap_buffers(struct ra_ctx *ctx)
+{
+ if (ctx->vo->x11->use_present)
+ present_sync_swap(ctx->vo->x11->present);
+}
+
+static void xlib_vk_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
+{
+ struct vo_x11_state *x11 = ctx->vo->x11;
+ if (ctx->vo->x11->use_present)
+ present_sync_get_info(x11->present, info);
+}
+
+static void xlib_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+ vo_x11_uninit(ctx->vo);
+}
+
+static bool xlib_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+
+ if (!mpvk_init(vk, ctx, VK_KHR_XLIB_SURFACE_EXTENSION_NAME))
+ goto error;
+
+ if (!vo_x11_init(ctx->vo))
+ goto error;
+
+ if (!vo_x11_create_vo_window(ctx->vo, NULL, "mpvk"))
+ goto error;
+
+ VkXlibSurfaceCreateInfoKHR xinfo = {
+ .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
+ .dpy = ctx->vo->x11->display,
+ .window = ctx->vo->x11->window,
+ };
+
+ struct ra_vk_ctx_params params = {
+ .check_visible = xlib_check_visible,
+ .swap_buffers = xlib_vk_swap_buffers,
+ .get_vsync = xlib_vk_get_vsync,
+ };
+
+ VkInstance inst = vk->vkinst->instance;
+ VkResult res = vkCreateXlibSurfaceKHR(inst, &xinfo, NULL, &vk->surface);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating Xlib surface\n");
+ goto error;
+ }
+
+ if (!ra_vk_ctx_init(ctx, vk, params, VK_PRESENT_MODE_FIFO_KHR))
+ goto error;
+
+ ra_add_native_resource(ctx->ra, "x11", ctx->vo->x11->display);
+
+ return true;
+
+error:
+ xlib_uninit(ctx);
+ return false;
+}
+
+static bool resize(struct ra_ctx *ctx)
+{
+ return ra_vk_ctx_resize(ctx, ctx->vo->dwidth, ctx->vo->dheight);
+}
+
+static bool xlib_reconfig(struct ra_ctx *ctx)
+{
+ vo_x11_config_vo_window(ctx->vo);
+ return resize(ctx);
+}
+
+static int xlib_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ int ret = vo_x11_control(ctx->vo, events, request, arg);
+ if (*events & VO_EVENT_RESIZE) {
+ if (!resize(ctx))
+ return VO_ERROR;
+ }
+ return ret;
+}
+
+static void xlib_wakeup(struct ra_ctx *ctx)
+{
+ vo_x11_wakeup(ctx->vo);
+}
+
+static void xlib_wait_events(struct ra_ctx *ctx, int64_t until_time_ns)
+{
+ vo_x11_wait_events(ctx->vo, until_time_ns);
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_xlib = {
+ .type = "vulkan",
+ .name = "x11vk",
+ .reconfig = xlib_reconfig,
+ .control = xlib_control,
+ .wakeup = xlib_wakeup,
+ .wait_events = xlib_wait_events,
+ .init = xlib_init,
+ .uninit = xlib_uninit,
+};
diff --git a/video/out/vulkan/utils.c b/video/out/vulkan/utils.c
new file mode 100644
index 0000000..57a3664
--- /dev/null
+++ b/video/out/vulkan/utils.c
@@ -0,0 +1,42 @@
+#include "video/out/placebo/utils.h"
+#include "utils.h"
+
+bool mpvk_init(struct mpvk_ctx *vk, struct ra_ctx *ctx, const char *surface_ext)
+{
+ vk->pllog = mppl_log_create(ctx, ctx->vo->log);
+ if (!vk->pllog)
+ goto error;
+
+ const char *exts[] = {
+ VK_KHR_SURFACE_EXTENSION_NAME,
+ surface_ext,
+ };
+
+ mppl_log_set_probing(vk->pllog, true);
+ vk->vkinst = pl_vk_inst_create(vk->pllog, &(struct pl_vk_inst_params) {
+ .debug = ctx->opts.debug,
+ .extensions = exts,
+ .num_extensions = MP_ARRAY_SIZE(exts),
+ });
+ mppl_log_set_probing(vk->pllog, false);
+ if (!vk->vkinst)
+ goto error;
+
+ return true;
+
+error:
+ mpvk_uninit(vk);
+ return false;
+}
+
+void mpvk_uninit(struct mpvk_ctx *vk)
+{
+ if (vk->surface) {
+ assert(vk->vkinst);
+ vkDestroySurfaceKHR(vk->vkinst->instance, vk->surface, NULL);
+ vk->surface = VK_NULL_HANDLE;
+ }
+
+ pl_vk_inst_destroy(&vk->vkinst);
+ pl_log_destroy(&vk->pllog);
+}
diff --git a/video/out/vulkan/utils.h b/video/out/vulkan/utils.h
new file mode 100644
index 0000000..a98e147
--- /dev/null
+++ b/video/out/vulkan/utils.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "common.h"
+#include "video/out/gpu/context.h"
+
+bool mpvk_init(struct mpvk_ctx *vk, struct ra_ctx *ctx, const char *surface_ext);
+void mpvk_uninit(struct mpvk_ctx *vk);