diff options
Diffstat (limited to 'video/out/vulkan')
-rw-r--r-- | video/out/vulkan/common.h | 40 | ||||
-rw-r--r-- | video/out/vulkan/context.c | 372 | ||||
-rw-r--r-- | video/out/vulkan/context.h | 31 | ||||
-rw-r--r-- | video/out/vulkan/context_android.c | 96 | ||||
-rw-r--r-- | video/out/vulkan/context_display.c | 491 | ||||
-rw-r--r-- | video/out/vulkan/context_mac.m | 119 | ||||
-rw-r--r-- | video/out/vulkan/context_wayland.c | 167 | ||||
-rw-r--r-- | video/out/vulkan/context_win.c | 106 | ||||
-rw-r--r-- | video/out/vulkan/context_xlib.c | 143 | ||||
-rw-r--r-- | video/out/vulkan/utils.c | 42 | ||||
-rw-r--r-- | video/out/vulkan/utils.h | 6 |
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); |