mirror of https://github.com/mpv-player/mpv
496 lines
15 KiB
C
496 lines
15 KiB
C
/*
|
|
* 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) {
|
|
.display = 0,
|
|
.mode = 0,
|
|
.plane = 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->ctx, &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_us)
|
|
{
|
|
// 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,
|
|
};
|