diff options
Diffstat (limited to 'video/out/vulkan/context.c')
-rw-r--r-- | video/out/vulkan/context.c | 372 |
1 files changed, 372 insertions, 0 deletions
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, +}; |