From 72d912de0b0b8825a91b676a3497f191dd00f9b3 Mon Sep 17 00:00:00 2001 From: flowreen Date: Sun, 26 Jul 2026 06:02:26 +0200 Subject: [PATCH 1/2] vf_d3d11vpp: create own device if the VO provides none The filter took its D3D11 device from the video output, so it failed to initialize on any other output, most notably --gpu-api=vulkan, where mpv then dropped the video track entirely. RTX Video HDR and RTX Video Super Resolution were unreachable outside of --gpu-api=d3d11. The video processor does not need to run on the VO's device. Fall back to a standalone device from hwcontext_fns_d3d11 and advertise it with hwdec_devices_add(), which lets the generic upload and download filters move frames in and out of it. That costs an upload and a download per frame, so the filter warns, and it still prefers the VO's device when there is one. Every instance registers its own mp_hwdec_ctx even when it shares another instance's device. Filters are created before the old ones are destroyed, so an instance that merely borrowed a peer's device is left with nothing registered once the peer deregisters, and the next upload probe fails. Sharing the device instead of creating one per instance keeps a chain of several d3d11vpp filters working, because an input view cannot be created from another device's texture. --- DOCS/man/vf.rst | 18 +++++++++++++-- video/filter/vf_d3d11vpp.c | 47 +++++++++++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index 086295d40ca5d..d050cdb9147cd 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -718,10 +718,24 @@ Available mpv-only filters are: Apply high quality VDPAU scaling (needs capable hardware). ``d3d11vpp`` - Direct3D 11 video post-processing. Requires a D3D11 context and works best - with hardware decoding. Software frames are automatically uploaded to hardware + Direct3D 11 video post-processing. Works best with a D3D11 context and + hardware decoding. Software frames are automatically uploaded to hardware for processing. + If the video output provides no D3D11 device, for example with + ``--gpu-api=vulkan``, the filter creates its own device. Every frame is + then copied between the two devices, directly on the GPU when the formats + involved allow it and through system memory otherwise, which costs memory + bandwidth and adds latency. Prefer ``--gpu-api=d3d11`` when using this + filter. + + In that fallback mode, frames from a different hardware decoder, such as + the CUDA frames ``nvdec`` produces, are routed through system memory on + their way to the D3D11 device, which costs yet another copy. + ``--hwdec=d3d11va`` cannot be used at all, because the decoder picks its + device before this filter is created, so it falls back to software + decoding. + ``format`` Convert to the selected image format, e.g., nv12, p010, etc. (default: don't change). Format names can be queried with ``--vf=d3d11vpp=format=help``. diff --git a/video/filter/vf_d3d11vpp.c b/video/filter/vf_d3d11vpp.c index 0b5ca24c3088d..002865108080d 100644 --- a/video/filter/vf_d3d11vpp.c +++ b/video/filter/vf_d3d11vpp.c @@ -115,6 +115,10 @@ struct priv { AVBufferRef *av_device_ref; AVBufferRef *hw_pool; + // Set only if the device was created by us instead of taken from the VO. + struct mp_hwdec_devices *hwdec_devs; + struct mp_hwdec_ctx own_hwctx; + struct mp_refqueue *queue; UINT num_past_views; @@ -697,6 +701,9 @@ static void uninit(struct mp_filter *vf) flush_frames(vf); talloc_free(p->queue); av_buffer_unref(&p->hw_pool); + + if (p->hwdec_devs) + hwdec_devices_remove(p->hwdec_devs, &p->own_hwctx); av_buffer_unref(&p->av_device_ref); if (p->video_ctx) @@ -757,9 +764,43 @@ static struct mp_filter *vf_d3d11vpp_create(struct mp_filter *parent, struct mp_hwdec_ctx *hwctx = hwdec_devices_get_by_imgfmt_and_type(info->hwdec_devs, IMGFMT_D3D11, AV_HWDEVICE_TYPE_D3D11VA); - if (!hwctx || !hwctx->av_device_ref) - goto fail; - p->av_device_ref = av_buffer_ref(hwctx->av_device_ref); + bool from_vo = hwctx && hwctx->av_device_ref && + !(hwctx->driver_name && + !strcmp(hwctx->driver_name, vf_d3d11vpp_filter.name)); + + if (from_vo) { + p->av_device_ref = av_buffer_ref(hwctx->av_device_ref); + } else { + // The VO does not provide a D3D11 device (e.g. --gpu-api=vulkan). + // Share one with any other instance of this filter, so that frames + // stay usable across a chain of them, or create one if we are first. + if (hwctx && hwctx->av_device_ref) { + p->av_device_ref = av_buffer_ref(hwctx->av_device_ref); + } else { + const struct hwcontext_fns *fns = + hwdec_get_hwcontext_fns(AV_HWDEVICE_TYPE_D3D11VA); + if (!fns || !fns->create_dev) + goto fail; + struct hwcontext_create_dev_params dev_params = {0}; + p->av_device_ref = fns->create_dev(f->global, f->log, &dev_params); + if (!p->av_device_ref) + goto fail; + MP_WARN(f, "No D3D11 video output, using a standalone device. " + "Frames are copied through system memory, which is " + "slow. Use --gpu-api=d3d11 to avoid this.\n"); + } + // Advertise the device so that the generic upload/download filters can + // move frames in and out of it. Every instance registers its own entry + // even when sharing a device: filters are created before the old ones + // are destroyed, so the instance we borrowed from may go away first. + p->own_hwctx = (struct mp_hwdec_ctx){ + .driver_name = vf_d3d11vpp_filter.name, + .av_device_ref = p->av_device_ref, + .hw_imgfmt = IMGFMT_D3D11, + }; + p->hwdec_devs = info->hwdec_devs; + hwdec_devices_add(p->hwdec_devs, &p->own_hwctx); + } AVHWDeviceContext *avhwctx = (void *)p->av_device_ref->data; AVD3D11VADeviceContext *d3dctx = avhwctx->hwctx; From 4d171211a8a6e188723d1b805c98af8e254d09b7 Mon Sep 17 00:00:00 2001 From: flowreen Date: Sun, 26 Jul 2026 13:14:00 +0200 Subject: [PATCH 2/2] vf_d3d11vpp: derive own device from the VO when possible --- video/filter/vf_d3d11vpp.c | 43 ++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/video/filter/vf_d3d11vpp.c b/video/filter/vf_d3d11vpp.c index 002865108080d..761eee3148386 100644 --- a/video/filter/vf_d3d11vpp.c +++ b/video/filter/vf_d3d11vpp.c @@ -777,15 +777,40 @@ static struct mp_filter *vf_d3d11vpp_create(struct mp_filter *parent, if (hwctx && hwctx->av_device_ref) { p->av_device_ref = av_buffer_ref(hwctx->av_device_ref); } else { - const struct hwcontext_fns *fns = - hwdec_get_hwcontext_fns(AV_HWDEVICE_TYPE_D3D11VA); - if (!fns || !fns->create_dev) - goto fail; - struct hwcontext_create_dev_params dev_params = {0}; - p->av_device_ref = fns->create_dev(f->global, f->log, &dev_params); - if (!p->av_device_ref) - goto fail; - MP_WARN(f, "No D3D11 video output, using a standalone device. " + // Prefer deriving from the VO's device, so that we land on the + // same adapter it is rendering on. Creating a device standalone + // picks whichever adapter enumerates first, which is not + // necessarily the same GPU on a multi-adapter system. + struct hwdec_imgfmt_request vk_params = { + .imgfmt = IMGFMT_VULKAN, + .probing = false, + }; + hwdec_devices_request_for_img_fmt(info->hwdec_devs, &vk_params); + struct mp_hwdec_ctx *vkctx = + hwdec_devices_get_by_imgfmt_and_type(info->hwdec_devs, + IMGFMT_VULKAN, + AV_HWDEVICE_TYPE_VULKAN); + if (vkctx && vkctx->av_device_ref) { + int ret = av_hwdevice_ctx_create_derived(&p->av_device_ref, + AV_HWDEVICE_TYPE_D3D11VA, + vkctx->av_device_ref, 0); + if (ret < 0) { + MP_VERBOSE(f, "Could not derive a D3D11 device from the " + "VO's Vulkan device: %s\n", av_err2str(ret)); + p->av_device_ref = NULL; + } + } + if (!p->av_device_ref) { + const struct hwcontext_fns *fns = + hwdec_get_hwcontext_fns(AV_HWDEVICE_TYPE_D3D11VA); + if (!fns || !fns->create_dev) + goto fail; + struct hwcontext_create_dev_params dev_params = {0}; + p->av_device_ref = fns->create_dev(f->global, f->log, &dev_params); + if (!p->av_device_ref) + goto fail; + } + MP_WARN(f, "No D3D11 video output, using a separate device. " "Frames are copied through system memory, which is " "slow. Use --gpu-api=d3d11 to avoid this.\n"); }