From 25fa1b0b45b5508148d2131e0906da320967aee8 Mon Sep 17 00:00:00 2001 From: Philip Langdale Date: Sun, 31 Jul 2022 13:47:23 -0700 Subject: [PATCH] hwdec/drmprime: add drmprime hwdec-interop In the confusing landscape of hardware video decoding APIs, we have had a long standing support gap for the v4l2 based APIs implemented for the various SoCs from Rockship, Amlogic, Allwinner, etc. While VAAPI is the defacto default for desktop GPUs, the developers who work on these SoCs (who are not the vendors!) have preferred to implement kernel APIs rather than maintain a userspace driver as VAAPI would require. While there are two v4l2 APIs (m2m and requests), and multiple forks of ffmpeg where support for those APIs languishes without reaching upstream, we can at least say that these APIs export frames as DRMPrime dmabufs, and that they use the ffmpeg drm hwcontext. With those two constants, it is possible for us to write a hwdec-interop without worrying about the mess underneath - for the most part. Accordingly, this change implements a hwdec-interop for any decoder that produces frames as DRMPrime dmabufs. The bulk of the heavy lifting is done by the dmabuf interop code we already had from supporting vaapi, and which I refactored for reusability in a previous set of changes. When we combine that with the fact that we can't probe for supported formats, the new code in this change is pretty simple. This change also includes the hwcontext_fns that are required for us to be able to configure the hwcontext used by `hwdec=drm-copy`. This is technically unrelated, but it seemed a good time to fill this gap. From a testing perspective, I have directly tested on a RockPRO64, while others have tested with different flavours of Rockchip and on Amlogic, providing m2m coverage. I have some other SoCs that I need to spin up to test with, but I don't expect big surprises, and when we inevitably need to account for new special cases down the line, we can do so - we won't be able to support every possible configuration blindly. --- DOCS/man/options.rst | 2 + meson.build | 4 +- video/drmprime.c | 44 ++++++ video/hwdec.c | 3 + video/hwdec.h | 1 + video/out/gpu/hwdec.c | 2 + video/out/hwdec/hwdec_drmprime.c | 261 +++++++++++++++++++++++++++++++ wscript_build.py | 2 + 8 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 video/drmprime.c create mode 100644 video/out/hwdec/hwdec_drmprime.c diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 59fc79c89e..8b88c5dc79 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -1253,6 +1253,8 @@ Video :vaapi-copy: copies video back into system RAM (Linux with some GPUs only) :nvdec: requires ``--vo=gpu`` (Any platform CUDA is available) :nvdec-copy: copies video back to system RAM (Any platform CUDA is available) + :drm: requires ``--vo=gpu`` (Linux only) + :drm-copy: copies video back to system RAM (Linux ony) Other hwdecs (only use if you know you have to): diff --git a/meson.build b/meson.build index 1ab8304613..4273ebcdae 100644 --- a/meson.build +++ b/meson.build @@ -943,10 +943,12 @@ drm += {'use': drm['deps'].found() and drm['header']} if drm['use'] dependencies += drm['deps'] features += drm['name'] - sources += files('video/out/drm_atomic.c', + sources += files('video/drmprime.c', + 'video/out/drm_atomic.c', 'video/out/drm_common.c', 'video/out/drm_prime.c', 'video/out/opengl/hwdec_drmprime_drm.c', + 'video/out/hwdec/hwdec_drmprime.c', 'video/out/vo_drm.c') endif diff --git a/video/drmprime.c b/video/drmprime.c new file mode 100644 index 0000000000..92bf1c9aa3 --- /dev/null +++ b/video/drmprime.c @@ -0,0 +1,44 @@ +/* + * 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 . + */ + +#include "config.h" + +#include + +#include "hwdec.h" +#include "options/m_config.h" +#include "video/out/drm_common.h" + +extern const struct m_sub_options drm_conf; +static struct AVBufferRef *drm_create_standalone(struct mpv_global *global, + struct mp_log *log, struct hwcontext_create_dev_params *params) +{ + void *tmp = talloc_new(NULL); + struct drm_opts *drm_opts = mp_get_config_group(tmp, global, &drm_conf); + const char *opt_path = drm_opts->drm_device_path; + talloc_free(tmp); + + const char *device_path = opt_path ? opt_path : "/dev/dri/renderD128"; + AVBufferRef* ref = NULL; + av_hwdevice_ctx_create(&ref, AV_HWDEVICE_TYPE_DRM, device_path, NULL, 0); + return ref; +} + +const struct hwcontext_fns hwcontext_fns_drmprime = { + .av_hwdevice_type = AV_HWDEVICE_TYPE_DRM, + .create_dev = drm_create_standalone, +}; diff --git a/video/hwdec.c b/video/hwdec.c index f9b1df32e2..46ddff516c 100644 --- a/video/hwdec.c +++ b/video/hwdec.c @@ -131,6 +131,9 @@ static const struct hwcontext_fns *const hwcontext_fns[] = { #if HAVE_D3D9_HWACCEL &hwcontext_fns_dxva2, #endif +#if HAVE_DRM + &hwcontext_fns_drmprime, +#endif #if HAVE_VAAPI &hwcontext_fns_vaapi, #endif diff --git a/video/hwdec.h b/video/hwdec.h index 8219fd5dab..3ceb5a6897 100644 --- a/video/hwdec.h +++ b/video/hwdec.h @@ -99,6 +99,7 @@ const struct hwcontext_fns *hwdec_get_hwcontext_fns(int av_hwdevice_type); extern const struct hwcontext_fns hwcontext_fns_cuda; extern const struct hwcontext_fns hwcontext_fns_d3d11; +extern const struct hwcontext_fns hwcontext_fns_drmprime; extern const struct hwcontext_fns hwcontext_fns_dxva2; extern const struct hwcontext_fns hwcontext_fns_vaapi; extern const struct hwcontext_fns hwcontext_fns_vdpau; diff --git a/video/out/gpu/hwdec.c b/video/out/gpu/hwdec.c index 14c36347c0..25bdf116c9 100644 --- a/video/out/gpu/hwdec.c +++ b/video/out/gpu/hwdec.c @@ -38,6 +38,7 @@ extern const struct ra_hwdec_driver ra_hwdec_dxva2dxgi; extern const struct ra_hwdec_driver ra_hwdec_cuda; extern const struct ra_hwdec_driver ra_hwdec_cuda_nvdec; extern const struct ra_hwdec_driver ra_hwdec_rpi_overlay; +extern const struct ra_hwdec_driver ra_hwdec_drmprime; extern const struct ra_hwdec_driver ra_hwdec_drmprime_drm; const struct ra_hwdec_driver *const ra_hwdec_drivers[] = { @@ -74,6 +75,7 @@ const struct ra_hwdec_driver *const ra_hwdec_drivers[] = { &ra_hwdec_rpi_overlay, #endif #if HAVE_DRM + &ra_hwdec_drmprime, &ra_hwdec_drmprime_drm, #endif diff --git a/video/out/hwdec/hwdec_drmprime.c b/video/out/hwdec/hwdec_drmprime.c new file mode 100644 index 0000000000..f663658ea0 --- /dev/null +++ b/video/out/hwdec/hwdec_drmprime.c @@ -0,0 +1,261 @@ +/* + * 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "config.h" + +#include "libmpv/render_gl.h" +#include "options/m_config.h" +#include "video/out/drm_common.h" +#include "video/out/gpu/hwdec.h" +#include "video/out/hwdec/dmabuf_interop.h" + +extern const struct m_sub_options drm_conf; + +struct priv_owner { + struct mp_hwdec_ctx hwctx; + int *formats; + + struct dmabuf_interop dmabuf_interop; +}; + +static void uninit(struct ra_hwdec *hw) +{ + struct priv_owner *p = hw->priv; + if (p->hwctx.driver_name) + hwdec_devices_remove(hw->devs, &p->hwctx); +} + +const static dmabuf_interop_init interop_inits[] = { +#if HAVE_DMABUF_INTEROP_GL + dmabuf_interop_gl_init, +#endif +#if HAVE_DMABUF_INTEROP_PL + dmabuf_interop_pl_init, +#endif + NULL +}; + +static int init(struct ra_hwdec *hw) +{ + struct priv_owner *p = hw->priv; + + for (int i = 0; interop_inits[i]; i++) { + if (interop_inits[i](hw, &p->dmabuf_interop)) { + break; + } + } + + if (!p->dmabuf_interop.interop_map || !p->dmabuf_interop.interop_unmap) { + MP_VERBOSE(hw, "drmprime hwdec requires at least one dmabuf interop backend.\n"); + return -1; + } + + /* + * The drm_params resource is not provided when using X11 or Wayland, but + * there are extensions that supposedly provide this information from the + * drivers. Not properly documented. Of course. + */ + mpv_opengl_drm_params_v2 *params = ra_get_native_resource(hw->ra, + "drm_params_v2"); + + /* + * Respect drm_device option, so there is a way to control this when not + * using a DRM gpu context. If drm_params_v2 are present, they will already + * respect this option. + */ + void *tmp = talloc_new(NULL); + struct drm_opts *drm_opts = mp_get_config_group(tmp, hw->global, &drm_conf); + const char *opt_path = drm_opts->drm_device_path; + talloc_free(tmp); + + const char *device_path = params && params->render_fd ? + drmGetRenderDeviceNameFromFd(params->render_fd) : + opt_path ? opt_path : "/dev/dri/renderD128"; + MP_VERBOSE(hw, "Using DRM device: %s\n", device_path); + + int ret = av_hwdevice_ctx_create(&p->hwctx.av_device_ref, + AV_HWDEVICE_TYPE_DRM, + device_path, NULL, 0); + if (ret != 0) { + MP_VERBOSE(hw, "Failed to create hwdevice_ctx: %s\n", av_err2str(ret)); + return -1; + } + + /* + * At the moment, there is no way to discover compatible formats + * from the hwdevice_ctx, and in fact the ffmpeg hwaccels hard-code + * formats too, so we're not missing out on anything. + */ + int num_formats = 0; + MP_TARRAY_APPEND(p, p->formats, num_formats, IMGFMT_NV12); + MP_TARRAY_APPEND(p, p->formats, num_formats, 0); // terminate it + + p->hwctx.hw_imgfmt = IMGFMT_DRMPRIME; + p->hwctx.supported_formats = p->formats; + p->hwctx.driver_name = hw->driver->name; + hwdec_devices_add(hw->devs, &p->hwctx); + + return 0; +} + +static void mapper_unmap(struct ra_hwdec_mapper *mapper) +{ + struct priv_owner *p_owner = mapper->owner->priv; + struct priv *p = mapper->priv; + + p_owner->dmabuf_interop.interop_unmap(mapper); + + if (p->surface_acquired) { + for (int n = 0; n < p->desc.nb_objects; n++) + close(p->desc.objects[n].fd); + p->surface_acquired = false; + } +} + +static void mapper_uninit(struct ra_hwdec_mapper *mapper) +{ + struct priv_owner *p_owner = mapper->owner->priv; + if (p_owner->dmabuf_interop.interop_uninit) { + p_owner->dmabuf_interop.interop_uninit(mapper); + } +} + +static bool check_fmt(struct ra_hwdec_mapper *mapper, int fmt) +{ + struct priv_owner *p_owner = mapper->owner->priv; + for (int n = 0; p_owner->formats && p_owner->formats[n]; n++) { + if (p_owner->formats[n] == fmt) + return true; + } + return false; +} + +static int mapper_init(struct ra_hwdec_mapper *mapper) +{ + struct priv_owner *p_owner = mapper->owner->priv; + struct priv *p = mapper->priv; + + mapper->dst_params = mapper->src_params; + mapper->dst_params.imgfmt = mapper->src_params.hw_subfmt; + mapper->dst_params.hw_subfmt = 0; + + struct ra_imgfmt_desc desc = {0}; + + if (!ra_get_imgfmt_desc(mapper->ra, mapper->dst_params.imgfmt, &desc)) + return -1; + + p->num_planes = desc.num_planes; + mp_image_set_params(&p->layout, &mapper->dst_params); + + if (p_owner->dmabuf_interop.interop_init) + if (!p_owner->dmabuf_interop.interop_init(mapper, &desc)) + return -1; + + if (!check_fmt(mapper, mapper->dst_params.imgfmt)) + { + MP_FATAL(mapper, "unsupported DRM image format %s\n", + mp_imgfmt_to_name(mapper->dst_params.imgfmt)); + return -1; + } + + return 0; +} + +static int mapper_map(struct ra_hwdec_mapper *mapper) +{ + struct priv_owner *p_owner = mapper->owner->priv; + struct priv *p = mapper->priv; + + /* + * Although we use the same AVDRMFrameDescriptor to hold the dmabuf + * properties, we additionally need to dup the fds to ensure the + * frame doesn't disappear out from under us. And then for clarity, + * we copy all the individual fields. + */ + const AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)mapper->src->planes[0]; + p->desc.nb_layers = desc->nb_layers; + p->desc.nb_objects = desc->nb_objects; + for (int i = 0; i < desc->nb_layers; i++) { + p->desc.layers[i].format = desc->layers[i].format; + p->desc.layers[i].nb_planes = desc->layers[i].nb_planes; + for (int j = 0; j < desc->layers[i].nb_planes; j++) { + p->desc.layers[i].planes[j].object_index = desc->layers[i].planes[j].object_index; + p->desc.layers[i].planes[j].offset = desc->layers[i].planes[j].offset; + p->desc.layers[i].planes[j].pitch = desc->layers[i].planes[j].pitch; + } + } + for (int i = 0; i < desc->nb_objects; i++) { + p->desc.objects[i].format_modifier = desc->objects[i].format_modifier; + p->desc.objects[i].fd = dup(desc->objects[i].fd); + p->desc.objects[i].size = desc->objects[i].size; + } + p->surface_acquired = true; + + // We can handle composed formats if the total number of planes is still + // equal the number of planes we expect. Complex formats with auxilliary + // planes cannot be supported. + + int num_returned_planes = 0; + for (int i = 0; i < p->desc.nb_layers; i++) { + num_returned_planes += p->desc.layers[i].nb_planes; + } + + if (p->num_planes != num_returned_planes) { + MP_ERR(mapper, + "Mapped surface with format '%s' has unexpected number of planes. " + "(%d layers and %d planes, but expected %d planes)\n", + mp_imgfmt_to_name(mapper->src->params.hw_subfmt), + p->desc.nb_layers, num_returned_planes, p->num_planes); + goto err; + } + + if (!p_owner->dmabuf_interop.interop_map(mapper, &p_owner->dmabuf_interop, + false)) + goto err; + + return 0; + +err: + mapper_unmap(mapper); + + MP_FATAL(mapper, "mapping DRM dmabuf failed\n"); + return -1; +} + +const struct ra_hwdec_driver ra_hwdec_drmprime = { + .name = "drmprime", + .priv_size = sizeof(struct priv_owner), + .imgfmts = {IMGFMT_DRMPRIME, 0}, + .init = init, + .uninit = uninit, + .mapper = &(const struct ra_hwdec_mapper_driver){ + .priv_size = sizeof(struct priv), + .init = mapper_init, + .uninit = mapper_uninit, + .map = mapper_map, + .unmap = mapper_unmap, + }, +}; diff --git a/wscript_build.py b/wscript_build.py index a5905cdab8..1b3ffd8e7b 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -417,6 +417,7 @@ def build(ctx): ( "video/csputils.c" ), ( "video/cuda.c", "cuda-hwaccel" ), ( "video/d3d.c", "d3d-hwaccel" ), + ( "video/drmprime.c", "drm" ), ( "video/decode/vd_lavc.c" ), ( "video/filter/refqueue.c" ), ( "video/filter/vf_d3d11vpp.c", "d3d-hwaccel" ), @@ -470,6 +471,7 @@ def build(ctx): ( "video/out/hwdec/hwdec_cuda.c", "cuda-interop" ), ( "video/out/hwdec/hwdec_cuda_gl.c", "cuda-interop && gl" ), ( "video/out/hwdec/hwdec_cuda_vk.c", "cuda-interop && vulkan" ), + ( "video/out/hwdec/hwdec_drmprime.c", "drm" ), ( "video/out/hwdec/hwdec_vaapi.c", "vaapi-egl || vaapi-libplacebo" ), ( "video/out/hwdec/dmabuf_interop_gl.c", "dmabuf-interop-gl" ), ( "video/out/hwdec/dmabuf_interop_pl.c", "dmabuf-interop-pl" ),