PR #25151 opened by JuliusBairaktaris URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25151 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25151.patch
The AMF filters work only on D3D11, DXVA2 and AMF frames, so a Vulkan decode chain has to leave Vulkan to use them. AMF supports Vulkan through AMFContext1::InitVulkan(), but hwcontext_amf uses it only for AMF's own device. - `avutil/hwcontext_amf: derive from Vulkan devices` - `avfilter/vf_amf_common: support Vulkan frames` - `avfilter/vf_frc_amf: run on Vulkan frames` The instance needs `VK_KHR_get_physical_device_properties2` and the device `VK_KHR_swapchain`; filters.texi documents both. Not covered: Linux (compiled only) and P010 input. The Vulkan validation errors raised inside AMF and the VQ enhancer's Vulkan leak are reported to AMD: https://github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/618, https://github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/620. vqe_amf with Vulkan and with AMF output gives identical framemd5 over 300 frames, and PSNR-Y 91.3 dB against the D3D11 path. 1200 frames, median of 3, Vulkan / D3D11: decode 1.145 / 1.180 s, with vqe_amf 1.275 / 1.209 s, with frc_amf 1.064 / 0.956 s. Tested on Windows 11, RX 9070 XT, driver 32.0.31041.1004. Written with Claude Opus 5.5 in Claude Code; `Assisted-by` trailers on the commits. >From ace5830a75b07e7f92c729f549a4561d163533a5 Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris <[email protected]> Date: Mon, 28 Sep 2026 06:36:06 +0200 Subject: [PATCH 1/3] avutil/hwcontext_amf: derive from Vulkan devices hwcontext_amf cannot derive from a Vulkan device, so the AMF filters cannot run on the application's VkDevice. Derive through AMFContext1::InitVulkan() with timeline semaphores, and make the AMF device lock take the queue lock of the compute queue AMF submits to. The lock callbacks exist only under FF_API_VULKAN_SYNC_QUEUES, like the locking in ff_vk_exec_submit(). InitVulkan() needs VK_KHR_get_physical_device_properties2 on the instance and VK_KHR_swapchain on the device; missing extensions are logged. Assisted-by: Claude Opus 5.5 Signed-off-by: Julius Bairaktaris <[email protected]> --- libavutil/hwcontext_amf.c | 139 +++++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/libavutil/hwcontext_amf.c b/libavutil/hwcontext_amf.c index 7f47df5b59..cc318caf5a 100644 --- a/libavutil/hwcontext_amf.c +++ b/libavutil/hwcontext_amf.c @@ -17,6 +17,7 @@ */ #include "mem.h" +#include "bprint.h" #include "buffer.h" #include "pixfmt.h" #include "pixdesc.h" @@ -34,6 +35,7 @@ #if CONFIG_VULKAN #include "hwcontext_vulkan.h" +#include <AMF/core/VulkanAMF.h> #endif #if CONFIG_D3D11VA #include "libavutil/hwcontext_d3d11va.h" @@ -108,6 +110,15 @@ typedef struct { void *dummy; } AMFFramesContext; +typedef struct AMFDevicePriv { + AVAMFDeviceContext p; +#if CONFIG_VULKAN + AMFVulkanDevice vk_device; + AVHWDeviceContext *vk_device_ctx; + uint32_t vk_queue_family; +#endif +} AMFDevicePriv; + typedef struct AVAMFFormatMap { enum AVPixelFormat av_format; enum AMF_SURFACE_FORMAT amf_format; @@ -493,6 +504,7 @@ static int amf_transfer_data_to(AVHWFramesContext *ctx, AVFrame *dst, static int amf_transfer_data_from(AVHWFramesContext *ctx, AVFrame *dst, const AVFrame *src) { + AVAMFDeviceContext *amf_ctx = ctx->device_ctx->hwctx; AMFSurface* surface = (AMFSurface*)src->data[0]; AMFPlane *plane; uint8_t *src_data[4]; @@ -506,7 +518,11 @@ static int amf_transfer_data_from(AVHWFramesContext *ctx, AVFrame *dst, if (src->hw_frames_ctx->data != (uint8_t *)ctx || dst->format != ctx->sw_format) return AVERROR(EINVAL); + if (amf_ctx->lock) + amf_ctx->lock(amf_ctx->lock_ctx); ret = surface->pVtbl->Convert(surface, AMF_MEMORY_HOST); + if (amf_ctx->unlock) + amf_ctx->unlock(amf_ctx->lock_ctx); AMF_RETURN_IF_FALSE(ctx, ret == AMF_OK, AVERROR_UNKNOWN, "Convert(amf::AMF_MEMORY_HOST) failed with error %d\n", AVERROR_UNKNOWN); planes = (int)surface->pVtbl->GetPlanesCount(surface); @@ -878,6 +894,123 @@ static int amf_init_from_d3d12_device(AVAMFDeviceContext* amf_ctx, AVHWDeviceCon } #endif +#if CONFIG_VULKAN +static int amf_vk_has_extension(const char * const *list, int nb, const char *name) +{ + for (int i = 0; i < nb; i++) + if (!strcmp(list[i], name)) + return 1; + return 0; +} + +static void amf_log_missing_vulkan_extensions(AVHWDeviceContext *child_device_ctx, AMFContext1 *context1) +{ + AVVulkanDeviceContext *hwctx = child_device_ctx->hwctx; + const char **exts = NULL; + amf_size nb_exts = 0; + AVBPrint missing; + + if (!amf_vk_has_extension(hwctx->enabled_inst_extensions, hwctx->nb_enabled_inst_extensions, + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) + av_log(child_device_ctx, AV_LOG_ERROR, "AMF needs the Vulkan instance extension %s.\n", + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + + if (AMF_IFACE_CALL(context1, GetVulkanDeviceExtensions, &nb_exts, NULL) != AMF_OK || !nb_exts) + return; + exts = av_calloc(nb_exts, sizeof(*exts)); + if (!exts) + return; + av_bprint_init(&missing, 0, AV_BPRINT_SIZE_AUTOMATIC); + if (AMF_IFACE_CALL(context1, GetVulkanDeviceExtensions, &nb_exts, exts) == AMF_OK) { + for (amf_size i = 0; i < nb_exts; i++) + if (!amf_vk_has_extension(hwctx->enabled_dev_extensions, hwctx->nb_enabled_dev_extensions, exts[i])) + av_bprintf(&missing, " %s", exts[i]); + } + if (missing.len) + av_log(child_device_ctx, AV_LOG_ERROR, "Device extensions AMF lists that are not enabled " + "(some may be core in the device's Vulkan version):%s\n", missing.str); + av_bprint_finalize(&missing, NULL); + av_free(exts); +} + +#if FF_API_VULKAN_SYNC_QUEUES +static void amf_vulkan_lock_queue(void *opaque) +{ + AMFDevicePriv *priv = opaque; + AVVulkanDeviceContext *hwctx = priv->vk_device_ctx->hwctx; + +FF_DISABLE_DEPRECATION_WARNINGS + hwctx->lock_queue(priv->vk_device_ctx, priv->vk_queue_family, 0); +FF_ENABLE_DEPRECATION_WARNINGS +} + +static void amf_vulkan_unlock_queue(void *opaque) +{ + AMFDevicePriv *priv = opaque; + AVVulkanDeviceContext *hwctx = priv->vk_device_ctx->hwctx; + +FF_DISABLE_DEPRECATION_WARNINGS + hwctx->unlock_queue(priv->vk_device_ctx, priv->vk_queue_family, 0); +FF_ENABLE_DEPRECATION_WARNINGS +} +#endif + +static int amf_init_from_vulkan_device(AVHWDeviceContext *device_ctx, AVHWDeviceContext *child_device_ctx) +{ + AMFDevicePriv *priv = device_ctx->hwctx; + AVAMFDeviceContext *amf_ctx = &priv->p; + AVVulkanDeviceContext *hwctx = child_device_ctx->hwctx; + AMFGuid guid = IID_AMFContext1(); + AMFContext1 *context1 = NULL; + AMF_RESULT res; + + for (int i = 0; i < hwctx->nb_qf; i++) { + if ((hwctx->qf[i].flags & VK_QUEUE_COMPUTE_BIT) && + !(hwctx->qf[i].flags & VK_QUEUE_GRAPHICS_BIT)) { + priv->vk_device_ctx = child_device_ctx; + priv->vk_queue_family = hwctx->qf[i].idx; +#if FF_API_VULKAN_SYNC_QUEUES +FF_DISABLE_DEPRECATION_WARNINGS + if (hwctx->lock_queue && hwctx->unlock_queue) { + amf_ctx->lock = amf_vulkan_lock_queue; + amf_ctx->unlock = amf_vulkan_unlock_queue; + amf_ctx->lock_ctx = priv; + } +FF_ENABLE_DEPRECATION_WARNINGS +#endif + break; + } + } + AMF_ASSIGN_PROPERTY_BOOL(res, amf_ctx->context, AMF_CONTEXT_VULKAN_USE_TIMELINE_SEMAPHORES, 1); + AMF_RETURN_IF_FALSE(child_device_ctx, res == AMF_OK, AVERROR_EXTERNAL, + "AMF does not support Vulkan timeline semaphores: %d\n", res); + + res = AMF_IFACE_CALL(amf_ctx->context, QueryInterface, &guid, (void**)&context1); + AMF_RETURN_IF_FALSE(child_device_ctx, res == AMF_OK, AVERROR_UNKNOWN, + "QueryInterface(AMFContext1) failed with error %d\n", res); + + priv->vk_device = (AMFVulkanDevice) { + .cbSizeof = sizeof(priv->vk_device), + .hInstance = hwctx->inst, + .hPhysicalDevice = hwctx->phys_dev, + .hDevice = hwctx->act_dev, + }; + res = AMF_IFACE_CALL(context1, InitVulkan, &priv->vk_device); + if (res == AMF_OK || res == AMF_ALREADY_INITIALIZED) { + AMF_IFACE_CALL(context1, Release); + av_log(child_device_ctx, AV_LOG_INFO, "AMF via Vulkan.\n"); + return 0; + } + + if (res == AMF_NOT_SUPPORTED) + av_log(child_device_ctx, AV_LOG_ERROR, "AMF via Vulkan is not supported on the given device.\n"); + else + av_log(child_device_ctx, AV_LOG_ERROR, "AMF failed to initialise on the given Vulkan device: %d.\n", res); + amf_log_missing_vulkan_extensions(child_device_ctx, context1); + AMF_IFACE_CALL(context1, Release); + return AVERROR(ENODEV); +} +#endif static int amf_device_derive(AVHWDeviceContext *device_ctx, AVHWDeviceContext *child_device_ctx, AVDictionary *opts, @@ -904,6 +1037,10 @@ static int amf_device_derive(AVHWDeviceContext *device_ctx, #if CONFIG_D3D12VA case AV_HWDEVICE_TYPE_D3D12VA: return amf_init_from_d3d12_device(amf_ctx, child_device_ctx); +#endif +#if CONFIG_VULKAN + case AV_HWDEVICE_TYPE_VULKAN: + return amf_init_from_vulkan_device(device_ctx, child_device_ctx); #endif default: av_log(child_device_ctx, AV_LOG_ERROR, "AMF initialisation from a %s device is not supported.\n", @@ -987,7 +1124,7 @@ const HWContextType ff_hwcontext_type_amf = { .type = AV_HWDEVICE_TYPE_AMF, .name = "AMF", - .device_hwctx_size = sizeof(AVAMFDeviceContext), + .device_hwctx_size = sizeof(AMFDevicePriv), .frames_hwctx_size = sizeof(AMFFramesContext), .device_create = amf_device_create, -- 2.54.0 >From 1908066bc5872709526c9c63bfb56ac8d6ed3412 Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris <[email protected]> Date: Mon, 28 Sep 2026 06:36:28 +0200 Subject: [PATCH 2/3] avfilter/vf_amf_common: support Vulkan frames vqe_amf, sr_amf and vpp_amf do not accept Vulkan frames. Accept AV_PIX_FMT_VULKAN, and output Vulkan frames when the filter device is a Vulkan device. AMF waits on the frame's timeline value and signals the next one, which is claimed in the AVVkFrame under the frame lock, so AMF's own semaphores are never exposed. Input is copied on AMF's queue into an AMF surface, because the components keep input surfaces across calls and do not reliably report the layout they leave a wrapped image in. The copy adds about 2% to 1200 frames of 1080p vqe_amf compared with a zero-copy wrap. Assisted-by: Claude Opus 5.5 Signed-off-by: Julius Bairaktaris <[email protected]> --- Changelog | 1 + doc/filters.texi | 12 ++ libavfilter/vf_amf_common.c | 240 +++++++++++++++++++++++++++++++++++- libavfilter/vf_sr_amf.c | 3 + libavfilter/vf_vpp_amf.c | 1 + libavfilter/vf_vqe_amf.c | 1 + 6 files changed, 252 insertions(+), 6 deletions(-) diff --git a/Changelog b/Changelog index 32e7c8bfe7..56bd63fa0e 100644 --- a/Changelog +++ b/Changelog @@ -19,6 +19,7 @@ version <next>: - Panasonic RW2 decoder - xHE-AAC encoding via libfdk-xhe-aac - jpegapp bitstream filter +- AMF filters on Vulkan frames version 9.0: diff --git a/doc/filters.texi b/doc/filters.texi index 7ab0cfaab7..eebf788773 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -15041,6 +15041,18 @@ Example: ffmpeg -i input.mp4 -vf vqe_amf=attenuation=0.4 -c:v hevc_amf output.mp4 @end example +The AMF filters also run on Vulkan frames when the AMF device is derived from a +Vulkan device. AMF requires the instance extension +@code{VK_KHR_get_physical_device_properties2} and the device extension +@code{VK_KHR_swapchain}, which the Vulkan device does not enable by default. +If initialisation fails, the filter lists the extensions AMF asks for that the +device lacks. With a Vulkan filter device the output frames are Vulkan frames: +@example +ffmpeg -init_hw_device vulkan=vk:0,instance_extensions=VK_KHR_surface+VK_KHR_get_physical_device_properties2,device_extensions=VK_KHR_swapchain \ + -filter_hw_device vk -hwaccel vulkan -hwaccel_output_format vulkan -i input.mp4 \ + -vf vqe_amf,hwdownload,format=nv12 output.y4m +@end example + @section framestep Select one frame every N-th frame. diff --git a/libavfilter/vf_amf_common.c b/libavfilter/vf_amf_common.c index e1d0b42ee3..30ef9f9233 100644 --- a/libavfilter/vf_amf_common.c +++ b/libavfilter/vf_amf_common.c @@ -42,6 +42,11 @@ #include "libavutil/hwcontext_d3d11va.h" #endif +#if CONFIG_VULKAN +#include "libavutil/hwcontext_vulkan.h" +#include <AMF/core/VulkanAMF.h> +#endif + int amf_filter_init(AVFilterContext *avctx) { AMFFilterContext *ctx = avctx->priv; @@ -96,6 +101,18 @@ void amf_filter_uninit(AVFilterContext *avctx) av_buffer_unref(&ctx->hwframes_out_ref); } +static void amf_lock(AMFFilterContext *ctx) +{ + if (ctx->amf_device_ctx->lock) + ctx->amf_device_ctx->lock(ctx->amf_device_ctx->lock_ctx); +} + +static void amf_unlock(AMFFilterContext *ctx) +{ + if (ctx->amf_device_ctx->unlock) + ctx->amf_device_ctx->unlock(ctx->amf_device_ctx->lock_ctx); +} + static int amf_queue_props(AVFilterContext *avctx, const AVFrame *in) { AMFFilterContext *ctx = avctx->priv; @@ -116,12 +133,15 @@ static int amf_queue_props(AVFilterContext *avctx, const AVFrame *in) static int amf_receive_surface(AVFilterContext *avctx, AMFComponent *component, AMFSurface **surface) { + AMFFilterContext *ctx = avctx->priv; AMFGuid guid = IID_AMFSurface(); AMFData *data = NULL; AMF_RESULT res; *surface = NULL; + amf_lock(ctx); res = component->pVtbl->QueryOutput(component, &data); + amf_unlock(ctx); AMF_RETURN_IF_FALSE(avctx, res == AMF_OK || res == AMF_REPEAT || res == AMF_EOF, AVERROR_UNKNOWN, "QueryOutput() failed with error %d\n", res); if (!data) @@ -229,10 +249,16 @@ static int amf_deliver_output(AVFilterContext *avctx) static int amf_submit_surface(AVFilterContext *avctx, AMFComponent *component, AMFSurface *surface, int (*deliver)(AVFilterContext *avctx)) { + AMFFilterContext *ctx = avctx->priv; AMF_RESULT res; int ret = 0; - while ((res = component->pVtbl->SubmitInput(component, (AMFData*)surface)) == AMF_INPUT_FULL) { + for (;;) { + amf_lock(ctx); + res = component->pVtbl->SubmitInput(component, (AMFData*)surface); + amf_unlock(ctx); + if (res != AMF_INPUT_FULL) + break; ret = deliver(avctx); if (ret < 0) break; @@ -265,10 +291,16 @@ static int amf_forward_converted(AVFilterContext *avctx) static int amf_drain_component(AVFilterContext *avctx, AMFComponent *component, int (*deliver)(AVFilterContext *avctx)) { + AMFFilterContext *ctx = avctx->priv; AMF_RESULT res; int ret; - while ((res = component->pVtbl->Drain(component)) == AMF_INPUT_FULL) { + for (;;) { + amf_lock(ctx); + res = component->pVtbl->Drain(component); + amf_unlock(ctx); + if (res != AMF_INPUT_FULL) + break; ret = deliver(avctx); if (ret < 0) return ret; @@ -387,11 +419,14 @@ int amf_setup_input_output_formats(AVFilterContext *avctx, int err; AVFilterFormats *input_formats; AVFilterFormats *output_formats; - static const enum AVPixelFormat output_pix_fmts[] = { + const enum AVPixelFormat *output_pix_fmts; + static const enum AVPixelFormat output_pix_fmts_amf[] = { AV_PIX_FMT_AMF_SURFACE, AV_PIX_FMT_NONE, }; + output_pix_fmts = output_pix_fmts_amf; + //in case if hw_device_ctx is set to DXVA2 we change order of pixel formats to set DXVA2 be chosen by default //The order is ignored if hw_frames_ctx is not NULL on the config_output stage if (avctx->hw_device_ctx) { @@ -419,6 +454,18 @@ int amf_setup_input_output_formats(AVFilterContext *avctx, input_pix_fmts = pix_fmts_dxva2; } break; + #endif + #if CONFIG_VULKAN + case AV_HWDEVICE_TYPE_VULKAN: + { + static const enum AVPixelFormat pix_fmts_vulkan[] = { + AV_PIX_FMT_VULKAN, + AV_PIX_FMT_NONE, + }; + input_pix_fmts = pix_fmts_vulkan; + output_pix_fmts = pix_fmts_vulkan; + } + break; #endif case AV_HWDEVICE_TYPE_AMF: break; @@ -563,7 +610,12 @@ int amf_init_filter_config(AVFilterLink *outlink, enum AVPixelFormat *in_format) else out_sw_format = outlink->format; } - ctx->hwframes_out_ref = av_hwframe_ctx_alloc(ctx->amf_device_ref); + if (outlink->format == AV_PIX_FMT_VULKAN) + ctx->hwframes_out_ref = av_hwframe_ctx_alloc(inl->hw_frames_ctx ? + ((AVHWFramesContext*)inl->hw_frames_ctx->data)->device_ref : + avctx->hw_device_ctx); + else + ctx->hwframes_out_ref = av_hwframe_ctx_alloc(ctx->amf_device_ref); if (!ctx->hwframes_out_ref) return AVERROR(ENOMEM); hwframes_out = (AVHWFramesContext*)ctx->hwframes_out_ref->data; @@ -572,7 +624,7 @@ int amf_init_filter_config(AVFilterLink *outlink, enum AVPixelFormat *in_format) { ctx->amf_device_ctx = hwdev_ctx->hwctx; } - hwframes_out->format = AV_PIX_FMT_AMF_SURFACE; + hwframes_out->format = outlink->format == AV_PIX_FMT_VULKAN ? AV_PIX_FMT_VULKAN : AV_PIX_FMT_AMF_SURFACE; hwframes_out->sw_format = out_sw_format; // in_sw_format is inlink->format for software input and the underlying @@ -609,6 +661,39 @@ void amf_free_amfsurface(void *opaque, uint8_t *data) surface->pVtbl->Release(surface); } +#if CONFIG_VULKAN +static int amf_wrap_vulkan_frame(AVFilterContext *avctx, const AVFrame *frame, + VkImageLayout amf_layout, AMFSurface **ppSurface); + +static int amf_copy_to_vulkan_frame(AVFilterContext *avctx, AMFSurface *src, AVFrame *frame) +{ + AMFFilterContext *ctx = avctx->priv; + AMFSurface *dst; + AMF_RESULT res; + int ret; + + ret = av_hwframe_get_buffer(ctx->hwframes_out_ref, frame, 0); + if (ret < 0) + return ret; + + ret = amf_wrap_vulkan_frame(avctx, frame, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &dst); + if (ret < 0) + return ret; + + amf_lock(ctx); + res = AMF_IFACE_CALL(src, Convert, AMF_MEMORY_VULKAN); + if (res == AMF_OK && AMF_IFACE_CALL(src, GetMemoryType) != AMF_MEMORY_VULKAN) + res = AMF_NOT_SUPPORTED; + if (res == AMF_OK) + res = AMF_IFACE_CALL(src, CopySurfaceRegion, dst, 0, 0, 0, 0, + frame->width, frame->height); + amf_unlock(ctx); + AMF_IFACE_CALL(dst, Release); + AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_EXTERNAL, "CopySurfaceRegion() failed with error %d\n", res); + return 0; +} +#endif + AVFrame *amf_amfsurface_to_avframe(AVFilterContext *avctx, AMFSurface* pSurface) { AVFrame *frame = av_frame_alloc(); @@ -634,6 +719,12 @@ AVFrame *amf_amfsurface_to_avframe(AVFilterContext *avctx, AMFSurface* pSurface) AV_BUFFER_FLAG_READONLY); if (!frame->buf[1]) goto fail; +#if CONFIG_VULKAN + } else if (hwframes_out->format == AV_PIX_FMT_VULKAN) { + if (amf_copy_to_vulkan_frame(avctx, pSurface, frame) < 0) + goto fail; + pSurface->pVtbl->Release(pSurface); +#endif } else { // FIXME: add processing of other hw formats av_log(ctx, AV_LOG_ERROR, "Unknown pixel format\n"); goto fail; @@ -739,16 +830,41 @@ static int amf_copy_d3d11_texture(AVFilterContext *avctx, const AVFrame *frame, } #endif -#if CONFIG_D3D11VA || CONFIG_DXVA2 +#if CONFIG_D3D11VA || CONFIG_DXVA2 || CONFIG_VULKAN typedef struct AMFFrameHolder { AMFSurfaceObserver observer; AVFrame *frame; +#if CONFIG_VULKAN + AMFVulkanSurface vk_surface; + AMFVulkanTimeline vk_timeline; + uint64_t vk_claimed; +#endif } AMFFrameHolder; static void AMF_STD_CALL amf_release_held_frame(AMFSurfaceObserver *observer, AMFSurface *surface) { AMFFrameHolder *holder = (AMFFrameHolder*)observer; +#if CONFIG_VULKAN + if (holder->frame && holder->frame->format == AV_PIX_FMT_VULKAN) { + AVHWFramesContext *frames = (AVHWFramesContext*)holder->frame->hw_frames_ctx->data; + AVVulkanDeviceContext *hwctx = frames->device_ctx->hwctx; + AVVkFrame *f = (AVVkFrame*)holder->frame->data[0]; + + if (holder->vk_timeline.uiCount < holder->vk_claimed) { + PFN_vkGetDeviceProcAddr get_dev_proc_addr = + (PFN_vkGetDeviceProcAddr)hwctx->get_proc_addr(hwctx->inst, "vkGetDeviceProcAddr"); + PFN_vkSignalSemaphore signal_semaphore = + (PFN_vkSignalSemaphore)get_dev_proc_addr(hwctx->act_dev, "vkSignalSemaphore"); + VkSemaphoreSignalInfo info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, + .semaphore = f->sem[0], + .value = holder->vk_claimed, + }; + signal_semaphore(hwctx->act_dev, &info); + } + } +#endif av_frame_free(&holder->frame); av_free(holder); } @@ -772,6 +888,107 @@ static int amf_hold_frame(const AVFrame *frame, AMFSurfaceObserver **observer) } #endif +#if CONFIG_VULKAN +static int amf_wrap_vulkan_frame(AVFilterContext *avctx, const AVFrame *frame, + VkImageLayout amf_layout, AMFSurface **ppSurface) +{ + AMFFilterContext *ctx = avctx->priv; + AVHWFramesContext *frames = (AVHWFramesContext*)frame->hw_frames_ctx->data; + AVVulkanFramesContext *vkfc = frames->hwctx; + AVVkFrame *f = (AVVkFrame*)frame->data[0]; + AMFGuid guid = IID_AMFContext1(); + AMFContext1 *context1 = NULL; + AMFSurfaceObserver *observer; + AMFFrameHolder *holder; + AMF_RESULT res; + int ret; + + if (f->img[1] != VK_NULL_HANDLE) { + av_log(avctx, AV_LOG_ERROR, "AMF needs single-image multiplane Vulkan frames.\n"); + return AVERROR(EINVAL); + } + + res = AMF_IFACE_CALL(ctx->amf_device_ctx->context, QueryInterface, &guid, (void**)&context1); + AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR_UNKNOWN, "QueryInterface(AMFContext1) failed with error %d\n", res); + + ret = amf_hold_frame(frame, &observer); + if (ret < 0) { + AMF_IFACE_CALL(context1, Release); + return ret; + } + holder = (AMFFrameHolder*)observer; + + vkfc->lock_frame(frames, f); + holder->vk_timeline = (AMFVulkanTimeline) { + .cbSizeof = sizeof(holder->vk_timeline), + .eExtensionType = AMF_VARIANT_TIMELINE_SEMAPHORE, + .uiCount = f->sem_value[0], + }; + holder->vk_surface = (AMFVulkanSurface) { + .cbSizeof = sizeof(holder->vk_surface), + .hImage = f->img[0], + .hMemory = f->mem[0], + .iSize = f->size[0], + .eFormat = vkfc->format[0], + .iWidth = frames->width, + .iHeight = frames->height, + .eCurrentLayout = f->layout[0], + .Sync = { + .cbSizeof = sizeof(holder->vk_surface.Sync), + .pNext = &holder->vk_timeline, + .hSemaphore = f->sem[0], + .bSubmitted = 1, + }, + }; + res = AMF_IFACE_CALL(context1, CreateSurfaceFromVulkanNative, &holder->vk_surface, ppSurface, observer); + if (res == AMF_OK) { + holder->vk_claimed = ++f->sem_value[0]; + f->layout[0] = amf_layout; + f->access[0] = VK_ACCESS_2_NONE; + } + vkfc->unlock_frame(frames, f); + AMF_IFACE_CALL(context1, Release); + if (res != AMF_OK) { + av_frame_free(&holder->frame); + av_free(holder); + } + AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR(ENOMEM), "CreateSurfaceFromVulkanNative() failed with error %d\n", res); + return 0; +} + +static int amf_copy_from_vulkan_frame(AVFilterContext *avctx, const AVFrame *frame, AMFSurface **ppSurface) +{ + AMFFilterContext *ctx = avctx->priv; + AVHWFramesContext *frames = (AVHWFramesContext*)frame->hw_frames_ctx->data; + AMFSurface *src, *dst; + AMF_RESULT res; + int ret; + + res = AMF_IFACE_CALL(ctx->amf_device_ctx->context, AllocSurface, AMF_MEMORY_VULKAN, + av_av_to_amf_format(frames->sw_format), frames->width, frames->height, &dst); + AMF_RETURN_IF_FALSE(avctx, res == AMF_OK, AVERROR(ENOMEM), "AllocSurface() failed with error %d\n", res); + + ret = amf_wrap_vulkan_frame(avctx, frame, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, &src); + if (ret < 0) { + AMF_IFACE_CALL(dst, Release); + return ret; + } + + amf_lock(ctx); + res = AMF_IFACE_CALL(src, CopySurfaceRegion, dst, 0, 0, 0, 0, frames->width, frames->height); + amf_unlock(ctx); + AMF_IFACE_CALL(src, Release); + if (res != AMF_OK) { + AMF_IFACE_CALL(dst, Release); + av_log(avctx, AV_LOG_ERROR, "CopySurfaceRegion() failed with error %d\n", res); + return AVERROR_EXTERNAL; + } + + *ppSurface = dst; + return 0; +} +#endif + int amf_avframe_to_amfsurface(AVFilterContext *avctx, const AVFrame *frame, AMFSurface** ppSurface) { AMFVariantStruct var = { 0 }; @@ -783,6 +1000,9 @@ int amf_avframe_to_amfsurface(AVFilterContext *avctx, const AVFrame *frame, AMFS #if CONFIG_D3D11VA || CONFIG_DXVA2 AMFSurfaceObserver *observer; #endif +#if CONFIG_VULKAN + int ret; +#endif switch (frame->format) { #if CONFIG_D3D11VA @@ -815,6 +1035,14 @@ int amf_avframe_to_amfsurface(AVFilterContext *avctx, const AVFrame *frame, AMFS hw_surface = 1; } break; +#endif +#if CONFIG_VULKAN + case AV_PIX_FMT_VULKAN: + ret = amf_copy_from_vulkan_frame(avctx, frame, &surface); + if (ret < 0) + return ret; + hw_surface = 1; + break; #endif case AV_PIX_FMT_AMF_SURFACE: { diff --git a/libavfilter/vf_sr_amf.c b/libavfilter/vf_sr_amf.c index d589fa9970..e0976992d5 100644 --- a/libavfilter/vf_sr_amf.c +++ b/libavfilter/vf_sr_amf.c @@ -77,6 +77,7 @@ static int amf_filter_query_formats(AVFilterContext *avctx) AV_PIX_FMT_AMF_SURFACE, AV_PIX_FMT_D3D11, AV_PIX_FMT_DXVA2_VLD, + AV_PIX_FMT_VULKAN, AV_PIX_FMT_RGBAF16, AV_PIX_FMT_NONE, }; @@ -89,6 +90,7 @@ static int amf_filter_query_formats(AVFilterContext *avctx) AV_PIX_FMT_AMF_SURFACE, AV_PIX_FMT_D3D11, AV_PIX_FMT_DXVA2_VLD, + AV_PIX_FMT_VULKAN, AV_PIX_FMT_NONE, }; enum AVPixelFormat pix_fmts_requested[] = { @@ -96,6 +98,7 @@ static int amf_filter_query_formats(AVFilterContext *avctx) AV_PIX_FMT_AMF_SURFACE, AV_PIX_FMT_D3D11, AV_PIX_FMT_DXVA2_VLD, + AV_PIX_FMT_VULKAN, AV_PIX_FMT_NONE, }; int i; diff --git a/libavfilter/vf_vpp_amf.c b/libavfilter/vf_vpp_amf.c index 1369c40012..eb46a3eb27 100644 --- a/libavfilter/vf_vpp_amf.c +++ b/libavfilter/vf_vpp_amf.c @@ -50,6 +50,7 @@ static int amf_filter_query_formats(AVFilterContext *avctx) AV_PIX_FMT_AMF_SURFACE, AV_PIX_FMT_D3D11, AV_PIX_FMT_DXVA2_VLD, + AV_PIX_FMT_VULKAN, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, AV_PIX_FMT_BGRA, diff --git a/libavfilter/vf_vqe_amf.c b/libavfilter/vf_vqe_amf.c index 0e5a55ada5..840b6b9ed5 100644 --- a/libavfilter/vf_vqe_amf.c +++ b/libavfilter/vf_vqe_amf.c @@ -67,6 +67,7 @@ static int amf_filter_query_formats(AVFilterContext *avctx) AV_PIX_FMT_AMF_SURFACE, AV_PIX_FMT_D3D11, AV_PIX_FMT_DXVA2_VLD, + AV_PIX_FMT_VULKAN, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, AV_PIX_FMT_NONE, -- 2.54.0 >From 22de7f1485598502ad7a1e5c589582375513080d Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris <[email protected]> Date: Mon, 28 Sep 2026 06:36:41 +0200 Subject: [PATCH 3/3] avfilter/vf_frc_amf: run on Vulkan frames frc_amf does not accept Vulkan frames. Accept AV_PIX_FMT_VULKAN and select FRC_ENGINE_VULKAN on a Vulkan context when no engine is given. FRC has its own engine enum, so the AMF_MEMORY_VULKAN value the other components take does not apply. engine_type is unchanged, so the Vulkan engine is never chosen on a D3D11 context. Assisted-by: Claude Opus 5.5 Signed-off-by: Julius Bairaktaris <[email protected]> --- doc/filters.texi | 3 ++- libavfilter/vf_frc_amf.c | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/filters.texi b/doc/filters.texi index eebf788773..0d58777cc3 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -14952,7 +14952,8 @@ Specify the engine used to run shaders. DirectX 11. @item dx12 -DirectX 12 (Default value). +DirectX 12 (Default value, except on a Vulkan device, where the Vulkan engine +is used). @end table @item enable diff --git a/libavfilter/vf_frc_amf.c b/libavfilter/vf_frc_amf.c index c7ef58b429..efe652b075 100644 --- a/libavfilter/vf_frc_amf.c +++ b/libavfilter/vf_frc_amf.c @@ -72,6 +72,7 @@ static int amf_filter_query_formats(AVFilterContext *avctx) AV_PIX_FMT_AMF_SURFACE, AV_PIX_FMT_D3D11, AV_PIX_FMT_DXVA2_VLD, + AV_PIX_FMT_VULKAN, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, AV_PIX_FMT_BGRA, @@ -95,7 +96,7 @@ static int amf_frc_filter_config_output(AVFilterLink *outlink) AMFFilterContext *amf_ctx = &frc_ctx->common; AVAMFDeviceContext *device_ctx = NULL; - int err; + int err, engine_type; AMF_RESULT res; enum AVPixelFormat in_format; @@ -122,8 +123,11 @@ static int amf_frc_filter_config_output(AVFilterLink *outlink) // Possible bug: FRC must be initialized enabled to be toggleable on the fly after init. AMF_FRC_ASSIGN_PROPERTY_INT64_CHECK(avctx, amf_filter, AMF_FRC_MODE, FRC_x2_PRESENT); - if (frc_ctx->engine_type != -1) - AMF_FRC_ASSIGN_PROPERTY_INT64_CHECK(avctx, amf_filter, AMF_FRC_ENGINE_TYPE, frc_ctx->engine_type); + engine_type = frc_ctx->engine_type; + if (engine_type == -1 && av_amf_get_memory_type(device_ctx) == AMF_MEMORY_VULKAN) + engine_type = FRC_ENGINE_VULKAN; + if (engine_type != -1) + AMF_FRC_ASSIGN_PROPERTY_INT64_CHECK(avctx, amf_filter, AMF_FRC_ENGINE_TYPE, engine_type); AMF_FRC_ASSIGN_PROPERTY_INT64_CHECK(avctx, amf_filter, AMF_FRC_ENABLE_FALLBACK, frc_ctx->fallback); -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
