From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id C3F0E4EE68 for ; Tue, 17 Feb 2026 23:11:51 +0000 (UTC) Authentication-Results: ffbox; dkim=fail (body hash mismatch (got b'FrUBbSEWyyaVu6hOlD/8JcjIb5gZizHVDaXnUt+3LKM=', expected b'nk+v6zeiC+3OPh/8MLK3AOzKXXgl2ubh/GJ9c3zBGZI=')) header.d=ffmpeg.org header.i=@ffmpeg.org header.a=rsa-sha256 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1771369886; h=mime-version : to : date : message-id : reply-to : subject : list-id : list-archive : list-archive : list-help : list-owner : list-post : list-subscribe : list-unsubscribe : from : cc : content-type : content-transfer-encoding : from; bh=FrUBbSEWyyaVu6hOlD/8JcjIb5gZizHVDaXnUt+3LKM=; b=bKe+POGl/TyPuZtnHqdHF01ToeZR24Yvnh1Ho+rDTVdbelsYERYDG9CG8Rva99Xy64Gyi laMfoHunl4htstx5/ubksoXgDvimIIaSu4vkAg/6kDdd9mwL8ryGUY0hZTM+ep4PBmE+ydJ nLVjg5TXlwLoGEXKeWyTvzFeb/2cniSlL6lgkj/5HTWqjEDaeoe5wfJILpiURcGDR3KhkWG gXOxWPd19BvgSwxnc7CW9dQnM0FMbThWScwbQyQLtcRAQNVUXIXWp9prqizsE8VGMqJO1rw TVll8PUzRQgYAc1rZTNS2EQNQFkddtaSio34F2ykCYBxUpf1i3wCaYJ1Cx0A== Received: from [172.18.0.3] (unknown [172.18.0.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id D11C969120B; Wed, 18 Feb 2026 01:11:26 +0200 (EET) ARC-Seal: i=1; cv=none; a=rsa-sha256; d=ffmpeg.org; s=arc; t=1771369870; b=lbq6dgGPI7GicPT1VKm2JBzv909dbf3t68+VVtTEjavIyWRyUvHCmWhBa5UYyqkOEgP/1 o1jPPNglbzQyCC57/ssbZDSdmBZnQ7spHR/gyF9RP/K6EGQ0VJeBuVgrQ0fuojUrkZXzX31 8MBCP+RCiBOFrW/7GVbor9aJQrjL5ZYp6/zil46BOoFjmxilWCB8jvx981EnTos2l9uvf8y JvpcrbPZxauasNgx+iP7OmTBWKqX9RJqoB1pgeUpKXFwDMdvKNZZbYB9OMGRE18XnP1y5Mv uJVgZmh90p46495CYgKfqlr8o+i+63gaOari7aiJEXKiQyFZJgHAalTNkINQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=ffmpeg.org; s=arc; t=1771369870; h=from : sender : reply-to : subject : date : message-id : to : cc : mime-version : content-type : content-transfer-encoding : content-id : content-description : resent-date : resent-from : resent-sender : resent-to : resent-cc : resent-message-id : in-reply-to : references : list-id : list-help : list-unsubscribe : list-subscribe : list-post : list-owner : list-archive; bh=9as5P+65UJO4Hlg/Ng2jEzw/sN7e+6zHi8WA+oPOJQk=; b=EkNfLVXTApU4NgNwbbvz3UnD4OzO1ypRulGojhHI7Wp9XaVYcmGcsvs30HI2B2uTJRzIB 5oA1P1EozOMg/+F6F8jrkSQ+cFk4b5jJpixLjxx2wLgCoVTkmT/MYV/5wx6gISCGng+i3pb R1xoKjMFZ4eKGUclfaAwE2DC4ajLWgKuHLU4Mdgwc/+gnJR2VQklaIp0iOUZH4KUeIzO5Wl 2IKYIupu3XxdgbfnvRm+vtgsMymJYBDXPLKWtFsXFNmfMIv3n84jqKmK4qtmyM1j5rNL9sI uwCX6DXEmFvbWpBt7cCEonM2aMKxwj+FkH36ZVSQqMFb3BxWf5GA24z0KtAQ== ARC-Authentication-Results: i=1; ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none; dmarc=pass header.from=ffmpeg.org policy.dmarc=quarantine Authentication-Results: ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none (Message is not ARC signed); dmarc=pass (Used From Domain Record) header.from=ffmpeg.org policy.dmarc=quarantine DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1771369863; h=content-type : mime-version : content-transfer-encoding : from : to : reply-to : subject : date : from; bh=nk+v6zeiC+3OPh/8MLK3AOzKXXgl2ubh/GJ9c3zBGZI=; b=Au4HsiLm5ZkRZQTblIdLrzosPO8AD7TizNADoeMz8MLTLSbwKqt/qR4iLWguqW9umi+rX 2lvQiPVPHSyy37dDIN14v46IF+/GHTg+Ymb7x6yJhKMqdE5q6QG0zQV8if8zm0Hy58NoKC4 KiAH+TEBgkUN01wDLJrjaNc5Tcir/NfocoVGcFCb9DtwPyaaYKHEvPSVKAZHogfPPBswpjM AmooKJV1dGV74HFv2anLy51E94T+vySEIyX4601rxRwxqnviaqWmQpzSpruDwaH75YLJK28 zxKMxivurcjFtLfx6kR1fcKDtk8RiplcEmydffhPVn2TsS3/4m3q3cwtFR1g== Received: from c8d966988b92 (code.ffmpeg.org [188.245.149.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id 1FA7C691187 for ; Wed, 18 Feb 2026 01:11:03 +0200 (EET) MIME-Version: 1.0 To: ffmpeg-devel@ffmpeg.org Date: Tue, 17 Feb 2026 23:11:02 -0000 Message-ID: <177136986335.25.8039557501675258717@29965ddac10e> Message-ID-Hash: 4C2BWDHDVVLAPUH3ZAFZHREAFTUABKB7 X-Message-ID-Hash: 4C2BWDHDVVLAPUH3ZAFZHREAFTUABKB7 X-MailFrom: code@ffmpeg.org X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-ffmpeg-devel.ffmpeg.org-0; header-match-ffmpeg-devel.ffmpeg.org-1; header-match-ffmpeg-devel.ffmpeg.org-2; header-match-ffmpeg-devel.ffmpeg.org-3; emergency; member-moderation X-Mailman-Version: 3.3.10 Precedence: list Reply-To: FFmpeg development discussions and patches Subject: [FFmpeg-devel] [PR] avfilter: add d3d12 deinterlace filter `deinterlace_d3d12` (PR #21778) List-Id: FFmpeg development discussions and patches Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Steven Xiao via ffmpeg-devel Cc: Steven Xiao Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Archived-At: List-Archive: List-Post: PR #21778 opened by Steven Xiao (younengxiao) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21778 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21778.patch This commit introduces a video filter `deinterlace_d3d12` that provides hardware-accelerated deinterlacing using the D3D12 Video Processor. The filter supports: - bob and custom (motion-adaptive)deinterlace modes - frame-rate and field-rate output - automatic interlace detection Sample command lines: 1. Software decode with hwupload: ffmpeg -init_hw_device d3d12va=d3d12 -i interlaced.ts \ -vf "format=nv12,hwupload,deinterlace_d3d12=mode=default,hwdownload,format=nv12" \ -c:v libx264 output.mp4 2. Full hardware pipeline: ffmpeg -hwaccel d3d12va -hwaccel_output_format d3d12 -i interlaced.ts \ -vf "deinterlace_d3d12=mode=custom:rate=field" \ -c:v h264_d3d12va output.mp4 Signed-off-by: younengxiao >>From 7d1e62021e1faf495c8ddd70b1bff280b391273b Mon Sep 17 00:00:00 2001 From: stevxiao Date: Tue, 17 Feb 2026 18:01:57 -0500 Subject: [PATCH] avfilter: add d3d12 deinterlace filter `deinterlace_d3d12` This commit introduces a video filter `deinterlace_d3d12` that provides hardware-accelerated deinterlacing using the D3D12 Video Processor. The filter supports: - bob and custom (motion-adaptive)deinterlace modes - frame-rate and field-rate output - automatic interlace detection Sample command lines: 1. Software decode with hwupload: ffmpeg -init_hw_device d3d12va=d3d12 -i interlaced.ts \ -vf "format=nv12,hwupload,deinterlace_d3d12=mode=default,hwdownload,format=nv12" \ -c:v libx264 output.mp4 2. Full hardware pipeline: ffmpeg -hwaccel d3d12va -hwaccel_output_format d3d12 -i interlaced.ts \ -vf "deinterlace_d3d12=mode=custom:rate=field" \ -c:v h264_d3d12va output.mp4 Signed-off-by: younengxiao --- Changelog | 1 + configure | 1 + libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/version.h | 2 +- libavfilter/vf_deinterlace_d3d12.c | 1118 ++++++++++++++++++++++++++++ 6 files changed, 1123 insertions(+), 1 deletion(-) create mode 100644 libavfilter/vf_deinterlace_d3d12.c diff --git a/Changelog b/Changelog index a9d68b369e..b06282a796 100644 --- a/Changelog +++ b/Changelog @@ -20,6 +20,7 @@ version : - JPEG-XS raw bitstream muxer and demuxer - IAMF Projection mode Ambisonic Audio Elements muxing and demuxing - Add vf_mestimate_d3d12 filter +- Add vf_deinterlace_d3d12 filter version 8.0: diff --git a/configure b/configure index da93aaf661..116988d82d 100755 --- a/configure +++ b/configure @@ -3520,6 +3520,7 @@ gfxcapture_filter_deps="cxx17 threads d3d11va IGraphicsCaptureItemInterop __x_AB gfxcapture_filter_extralibs="-lstdc++" scale_d3d11_filter_deps="d3d11va" scale_d3d12_filter_deps="d3d12va ID3D12VideoProcessor" +deinterlace_d3d12_filter_deps="d3d12va ID3D12VideoProcessor" mestimate_d3d12_filter_deps="d3d12va ID3D12VideoMotionEstimator d3d12_motion_estimator" amf_deps_any="libdl LoadLibrary" diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 6ecacc346b..a530cfae29 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -276,6 +276,7 @@ OBJS-$(CONFIG_DECONVOLVE_FILTER) += vf_convolve.o framesync.o OBJS-$(CONFIG_DEDOT_FILTER) += vf_dedot.o OBJS-$(CONFIG_DEFLATE_FILTER) += vf_neighbor.o OBJS-$(CONFIG_DEFLICKER_FILTER) += vf_deflicker.o +OBJS-$(CONFIG_DEINTERLACE_D3D12_FILTER) += vf_deinterlace_d3d12.o OBJS-$(CONFIG_DEINTERLACE_QSV_FILTER) += vf_vpp_qsv.o OBJS-$(CONFIG_DEINTERLACE_VAAPI_FILTER) += vf_deinterlace_vaapi.o vaapi_vpp.o OBJS-$(CONFIG_DEJUDDER_FILTER) += vf_dejudder.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 458f8c5373..e26859e159 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -252,6 +252,7 @@ extern const FFFilter ff_vf_dedot; extern const FFFilter ff_vf_deflate; extern const FFFilter ff_vf_deflicker; extern const FFFilter ff_vf_deinterlace_qsv; +extern const FFFilter ff_vf_deinterlace_d3d12; extern const FFFilter ff_vf_deinterlace_vaapi; extern const FFFilter ff_vf_dejudder; extern const FFFilter ff_vf_delogo; diff --git a/libavfilter/version.h b/libavfilter/version.h index 64cd692ab6..537df129cd 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -31,7 +31,7 @@ #include "version_major.h" -#define LIBAVFILTER_VERSION_MINOR 12 +#define LIBAVFILTER_VERSION_MINOR 13 #define LIBAVFILTER_VERSION_MICRO 100 diff --git a/libavfilter/vf_deinterlace_d3d12.c b/libavfilter/vf_deinterlace_d3d12.c new file mode 100644 index 0000000000..fd713876fc --- /dev/null +++ b/libavfilter/vf_deinterlace_d3d12.c @@ -0,0 +1,1118 @@ +/* + * D3D12VA deinterlacing filter + * + * Copyright (c) 2026 Advanced Micro Devices, Inc. + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define COBJMACROS + +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" + +#include "libavutil/hwcontext.h" +#include "libavutil/hwcontext_d3d12va.h" + +#include "filters.h" +#include "video.h" + +#define MAX_REFERENCES 8 + +/** + * Deinterlace mode enumeration + * Maps to D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG values + */ +enum DeinterlaceD3D12Mode { + DEINT_D3D12_MODE_DEFAULT = 0, // Use best available mode + DEINT_D3D12_MODE_BOB = 1, // Bob deinterlacing (simple field interpolation) + DEINT_D3D12_MODE_CUSTOM = 2, // Driver-defined advanced deinterlacing +}; + +typedef struct DeinterlaceD3D12Context { + const AVClass *classCtx; + + /* Filter options */ + int mode; // Deinterlace mode (default, bob, custom) + int field_rate; // Output field rate (1 = frame rate, 2 = field rate) + int auto_enable; // Only deinterlace interlaced frames + + /* D3D12 objects */ + ID3D12Device *device; + ID3D12VideoDevice *video_device; + ID3D12VideoProcessor *video_processor; + ID3D12CommandQueue *command_queue; + ID3D12VideoProcessCommandList *command_list; + ID3D12CommandAllocator *command_allocator; + + /* Synchronization */ + ID3D12Fence *fence; + UINT64 fence_value; + HANDLE fence_event; + + /* Buffer references */ + AVBufferRef *hw_device_ctx; + AVBufferRef *hw_frames_ctx_out; + + /* Dimensions and formats */ + int width, height; + DXGI_FORMAT input_format; + + /* Color space and frame rate */ + DXGI_COLOR_SPACE_TYPE input_colorspace; + AVRational input_framerate; + + /* Video processor capabilities */ + D3D12_FEATURE_DATA_VIDEO_PROCESS_SUPPORT process_support; + D3D12_VIDEO_PROCESS_DEINTERLACE_FLAGS supported_deint_flags; + + /* Frame queue for temporal references */ + int queue_depth; + int queue_count; + AVFrame *frame_queue[MAX_REFERENCES]; + + /* State tracking */ + int eof; + int64_t prev_pts; + int num_past_frames; + int num_future_frames; + int current_frame_index; + int extra_delay_for_timestamps; + int initial_fill_done; + + /* Processor configured flag */ + int processor_configured; +} DeinterlaceD3D12Context; + +static av_cold int deint_d3d12_init(AVFilterContext *ctx) +{ + DeinterlaceD3D12Context *s = ctx->priv; + s->fence_value = 1; + s->processor_configured = 0; + return 0; +} + +static void release_d3d12_resources(DeinterlaceD3D12Context *s) +{ + UINT64 fence_value; + HRESULT hr; + + /* Wait for all GPU operations to complete before releasing resources */ + if (s->command_queue && s->fence && s->fence_event) { + fence_value = s->fence_value; + hr = ID3D12CommandQueue_Signal(s->command_queue, s->fence, fence_value); + if (SUCCEEDED(hr)) { + UINT64 completed = ID3D12Fence_GetCompletedValue(s->fence); + if (completed < fence_value) { + hr = ID3D12Fence_SetEventOnCompletion(s->fence, fence_value, s->fence_event); + if (SUCCEEDED(hr)) { + WaitForSingleObject(s->fence_event, INFINITE); + } + } + } + } + + if (s->fence_event) { + CloseHandle(s->fence_event); + s->fence_event = NULL; + } + + if (s->fence) { + ID3D12Fence_Release(s->fence); + s->fence = NULL; + } + + if (s->command_list) { + ID3D12VideoProcessCommandList_Release(s->command_list); + s->command_list = NULL; + } + + if (s->command_allocator) { + ID3D12CommandAllocator_Release(s->command_allocator); + s->command_allocator = NULL; + } + + if (s->video_processor) { + ID3D12VideoProcessor_Release(s->video_processor); + s->video_processor = NULL; + } + + if (s->video_device) { + ID3D12VideoDevice_Release(s->video_device); + s->video_device = NULL; + } + + if (s->command_queue) { + ID3D12CommandQueue_Release(s->command_queue); + s->command_queue = NULL; + } +} + +static void deint_d3d12_clear_queue(DeinterlaceD3D12Context *s) +{ + for (int i = 0; i < s->queue_count; i++) { + av_frame_free(&s->frame_queue[i]); + } + s->queue_count = 0; +} + +static DXGI_COLOR_SPACE_TYPE get_dxgi_colorspace(enum AVColorSpace colorspace, + enum AVColorTransferCharacteristic trc, + int is_10bit) +{ + /* Map FFmpeg color space to DXGI color space */ + if (is_10bit) { + /* 10-bit formats (P010) */ + if (colorspace == AVCOL_SPC_BT2020_NCL || colorspace == AVCOL_SPC_BT2020_CL) { + if (trc == AVCOL_TRC_SMPTE2084) { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020; // HDR10 + } else if (trc == AVCOL_TRC_ARIB_STD_B67) { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020; // HLG + } else { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020; + } + } else { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709; // Rec.709 10-bit + } + } else { + /* 8-bit formats (NV12) */ + if (colorspace == AVCOL_SPC_BT2020_NCL || colorspace == AVCOL_SPC_BT2020_CL) { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020; + } else if (colorspace == AVCOL_SPC_BT470BG || colorspace == AVCOL_SPC_SMPTE170M) { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601; + } else { + return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709; // Default to Rec.709 + } + } +} + +static AVRational get_input_framerate(AVFilterContext *ctx, AVFilterLink *inlink, AVFrame *in) +{ + AVRational framerate = {0, 0}; + + if (in->duration > 0 && inlink->time_base.num > 0 && inlink->time_base.den > 0) { + av_reduce(&framerate.num, &framerate.den, + inlink->time_base.den, in->duration * inlink->time_base.num, + INT_MAX); + } else if (inlink->time_base.num > 0 && inlink->time_base.den > 0) { + framerate.num = inlink->time_base.den; + framerate.den = inlink->time_base.num; + } else { + framerate.num = 30; + framerate.den = 1; + av_log(ctx, AV_LOG_WARNING, "Input framerate not determinable, defaulting to 30fps\n"); + } + + return framerate; +} + +static D3D12_VIDEO_PROCESS_DEINTERLACE_FLAGS get_deint_mode(DeinterlaceD3D12Context *s, + AVFilterContext *ctx) +{ + D3D12_VIDEO_PROCESS_DEINTERLACE_FLAGS mode_flag; + + switch (s->mode) { + case DEINT_D3D12_MODE_BOB: + mode_flag = D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_BOB; + break; + case DEINT_D3D12_MODE_CUSTOM: + mode_flag = D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_CUSTOM; + break; + case DEINT_D3D12_MODE_DEFAULT: + default: + /* Select best available mode */ + if (s->supported_deint_flags & D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_CUSTOM) { + mode_flag = D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_CUSTOM; + av_log(ctx, AV_LOG_VERBOSE, "Using custom (driver-defined) deinterlacing\n"); + } else if (s->supported_deint_flags & D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_BOB) { + mode_flag = D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_BOB; + av_log(ctx, AV_LOG_VERBOSE, "Using bob deinterlacing\n"); + } else { + mode_flag = D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_BOB; + av_log(ctx, AV_LOG_WARNING, "No deinterlacing modes reported, trying bob\n"); + } + break; + } + + /* Verify requested mode is supported */ + if (mode_flag != D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_BOB && + !(s->supported_deint_flags & mode_flag)) { + av_log(ctx, AV_LOG_WARNING, "Requested deinterlace mode not supported, falling back to bob\n"); + mode_flag = D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_BOB; + } + + return mode_flag; +} + +static int deint_d3d12_configure_processor(DeinterlaceD3D12Context *s, + AVFilterContext *ctx, + AVFrame *in) +{ + HRESULT hr; + AVHWDeviceContext *hwctx = (AVHWDeviceContext *)s->hw_device_ctx->data; + AVD3D12VADeviceContext *d3d12_hwctx = (AVD3D12VADeviceContext *)hwctx->hwctx; + D3D12_VIDEO_PROCESS_DEINTERLACE_FLAGS deint_mode; + D3D12_VIDEO_FIELD_TYPE field_type; + + s->device = d3d12_hwctx->device; + + av_log(ctx, AV_LOG_VERBOSE, "Configuring D3D12 deinterlace processor: %dx%d\n", + s->width, s->height); + + hr = ID3D12Device_QueryInterface(s->device, &IID_ID3D12VideoDevice, (void **)&s->video_device); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to get D3D12 video device interface: HRESULT 0x%lX\n", hr); + return AVERROR_EXTERNAL; + } + + D3D12_COMMAND_QUEUE_DESC queue_desc = { + .Type = D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 + }; + + hr = ID3D12Device_CreateCommandQueue(s->device, &queue_desc, &IID_ID3D12CommandQueue, (void **)&s->command_queue); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create command queue: HRESULT 0x%lX\n", hr); + return AVERROR_EXTERNAL; + } + + /* Determine field type from input frame */ + if (in->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST) { + field_type = D3D12_VIDEO_FIELD_TYPE_INTERLACED_TOP_FIELD_FIRST; + av_log(ctx, AV_LOG_VERBOSE, "Input field order: Top Field First\n"); + } else { + field_type = D3D12_VIDEO_FIELD_TYPE_INTERLACED_BOTTOM_FIELD_FIRST; + av_log(ctx, AV_LOG_VERBOSE, "Input field order: Bottom Field First\n"); + } + + /* Check deinterlacing support */ + s->process_support.NodeIndex = 0; + s->process_support.InputSample.Format.Format = s->input_format; + s->process_support.InputSample.Format.ColorSpace = s->input_colorspace; + s->process_support.InputSample.Width = s->width; + s->process_support.InputSample.Height = s->height; + s->process_support.InputFrameRate.Numerator = s->input_framerate.num; + s->process_support.InputFrameRate.Denominator = s->input_framerate.den; + s->process_support.InputFieldType = field_type; + s->process_support.InputStereoFormat = D3D12_VIDEO_FRAME_STEREO_FORMAT_NONE; + + s->process_support.OutputFormat.Format = s->input_format; + s->process_support.OutputFormat.ColorSpace = s->input_colorspace; + s->process_support.OutputFrameRate.Numerator = s->input_framerate.num * s->field_rate; + s->process_support.OutputFrameRate.Denominator = s->input_framerate.den; + s->process_support.OutputStereoFormat = D3D12_VIDEO_FRAME_STEREO_FORMAT_NONE; + + hr = ID3D12VideoDevice_CheckFeatureSupport( + s->video_device, + D3D12_FEATURE_VIDEO_PROCESS_SUPPORT, + &s->process_support, + sizeof(s->process_support) + ); + + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Video process feature check failed: HRESULT 0x%lX\n", hr); + return AVERROR_EXTERNAL; + } + + if (!(s->process_support.SupportFlags & D3D12_VIDEO_PROCESS_SUPPORT_FLAG_SUPPORTED)) { + av_log(ctx, AV_LOG_ERROR, "Video process configuration not supported by hardware\n"); + return AVERROR(ENOSYS); + } + + /* Store supported deinterlace flags */ + s->supported_deint_flags = s->process_support.DeinterlaceSupport; + + av_log(ctx, AV_LOG_VERBOSE, "Deinterlace support flags: 0x%X\n", s->supported_deint_flags); + + if (!(s->supported_deint_flags & (D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_BOB | + D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_CUSTOM))) { + av_log(ctx, AV_LOG_ERROR, "No deinterlacing modes supported by hardware\n"); + return AVERROR(ENOSYS); + } + + deint_mode = get_deint_mode(s, ctx); + + /* Query reference frame requirements from hardware */ + D3D12_FEATURE_DATA_VIDEO_PROCESS_REFERENCE_INFO ref_info = { + .NodeIndex = 0, + .DeinterlaceMode = deint_mode, + .Filters = D3D12_VIDEO_PROCESS_FILTER_FLAG_NONE, + .FeatureSupport = D3D12_VIDEO_PROCESS_FEATURE_FLAG_NONE, + .InputFrameRate = { s->input_framerate.num, s->input_framerate.den }, + .OutputFrameRate = { s->input_framerate.num * s->field_rate, s->input_framerate.den }, + .EnableAutoProcessing = FALSE, + }; + + hr = ID3D12VideoDevice_CheckFeatureSupport( + s->video_device, + D3D12_FEATURE_VIDEO_PROCESS_REFERENCE_INFO, + &ref_info, + sizeof(ref_info) + ); + + if (SUCCEEDED(hr)) { + s->num_past_frames = ref_info.PastFrames; + s->num_future_frames = ref_info.FutureFrames; + av_log(ctx, AV_LOG_VERBOSE, + "Reference frames from hardware: past=%d, future=%d\n", + s->num_past_frames, s->num_future_frames); + } else { + av_log(ctx, AV_LOG_WARNING, + "Failed to query reference info (HRESULT 0x%lX), using defaults\n", hr); + s->num_past_frames = (deint_mode == D3D12_VIDEO_PROCESS_DEINTERLACE_FLAG_CUSTOM) ? 1 : 0; + s->num_future_frames = 0; + } + + /* May need 1 extra slot for PTS calculation.*/ + s->extra_delay_for_timestamps = (s->field_rate == 2 && s->num_future_frames == 0) ? 1 : 0; + + s->queue_depth = s->num_past_frames + s->num_future_frames + s->extra_delay_for_timestamps + 1; + + if (s->queue_depth > MAX_REFERENCES) { + av_log(ctx, AV_LOG_ERROR, "Required queue depth (%d) exceeds maximum (%d)\n", + s->queue_depth, MAX_REFERENCES); + return AVERROR(ENOSYS); + } + + s->current_frame_index = s->num_past_frames; + + av_log(ctx, AV_LOG_VERBOSE, "Queue depth: %d (past: %d, future: %d, extra: %d)\n", + s->queue_depth, s->num_past_frames, s->num_future_frames, + s->extra_delay_for_timestamps); + + D3D12_VIDEO_PROCESS_OUTPUT_STREAM_DESC processor_output_desc = { + .Format = s->input_format, + .ColorSpace = s->input_colorspace, + .AlphaFillMode = D3D12_VIDEO_PROCESS_ALPHA_FILL_MODE_OPAQUE, + .AlphaFillModeSourceStreamIndex = 0, + .BackgroundColor = { 0.0f, 0.0f, 0.0f, 1.0f }, + .FrameRate = { s->input_framerate.num * s->field_rate, s->input_framerate.den }, + .EnableStereo = FALSE, + }; + + D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC processor_input_desc = { + .Format = s->input_format, + .ColorSpace = s->input_colorspace, + .SourceAspectRatio = { s->width, s->height }, + .DestinationAspectRatio = { s->width, s->height }, + .FrameRate = { s->input_framerate.num, s->input_framerate.den }, + .StereoFormat = D3D12_VIDEO_FRAME_STEREO_FORMAT_NONE, + .FieldType = field_type, + .DeinterlaceMode = deint_mode, + .EnableOrientation = FALSE, + .FilterFlags = D3D12_VIDEO_PROCESS_FILTER_FLAG_NONE, + .SourceSizeRange = { + .MaxWidth = s->width, + .MaxHeight = s->height, + .MinWidth = s->width, + .MinHeight = s->height + }, + .DestinationSizeRange = { + .MaxWidth = s->width, + .MaxHeight = s->height, + .MinWidth = s->width, + .MinHeight = s->height + }, + .EnableAlphaBlending = FALSE, + .LumaKey = { .Enable = FALSE, .Lower = 0.0f, .Upper = 1.0f }, + .NumPastFrames = s->num_past_frames, + .NumFutureFrames = s->num_future_frames, + .EnableAutoProcessing = FALSE, + }; + + hr = ID3D12VideoDevice_CreateVideoProcessor( + s->video_device, + 0, + &processor_output_desc, + 1, + &processor_input_desc, + &IID_ID3D12VideoProcessor, + (void **)&s->video_processor + ); + + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create video processor: HRESULT 0x%lX\n", hr); + return AVERROR_EXTERNAL; + } + + hr = ID3D12Device_CreateCommandAllocator( + s->device, + D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS, + &IID_ID3D12CommandAllocator, + (void **)&s->command_allocator + ); + + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create command allocator: HRESULT 0x%lX\n", hr); + return AVERROR_EXTERNAL; + } + + hr = ID3D12Device_CreateCommandList( + s->device, + 0, + D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS, + s->command_allocator, + NULL, + &IID_ID3D12VideoProcessCommandList, + (void **)&s->command_list + ); + + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create command list: HRESULT 0x%lX\n", hr); + return AVERROR_EXTERNAL; + } + + ID3D12VideoProcessCommandList_Close(s->command_list); + + hr = ID3D12Device_CreateFence(s->device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, (void **)&s->fence); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create fence: HRESULT 0x%lX\n", hr); + return AVERROR_EXTERNAL; + } + + s->fence_value = 1; + + s->fence_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!s->fence_event) { + av_log(ctx, AV_LOG_ERROR, "Failed to create fence event\n"); + return AVERROR_EXTERNAL; + } + + s->processor_configured = 1; + av_log(ctx, AV_LOG_VERBOSE, "D3D12 deinterlace processor successfully configured\n"); + return 0; +} + +static void add_resource_barrier(D3D12_RESOURCE_BARRIER *barriers, int *count, + ID3D12Resource *resource, + D3D12_RESOURCE_STATES before, + D3D12_RESOURCE_STATES after) +{ + barriers[(*count)++] = (D3D12_RESOURCE_BARRIER) { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = { + .pResource = resource, + .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + .StateBefore = before, + .StateAfter = after + } + }; +} + +static int deint_d3d12_process_frame(AVFilterContext *ctx, + AVFilterLink *outlink, + AVFrame *input_frame, + int field, + int queue_idx) +{ + DeinterlaceD3D12Context *s = ctx->priv; + AVFrame *out = NULL; + int ret = 0; + int i; + HRESULT hr; + + AVD3D12VAFrame *in_d3d12_frame = (AVD3D12VAFrame *)input_frame->data[0]; + + out = av_frame_alloc(); + if (!out) { + av_log(ctx, AV_LOG_ERROR, "Failed to allocate output frame\n"); + return AVERROR(ENOMEM); + } + + ret = av_hwframe_get_buffer(s->hw_frames_ctx_out, out, 0); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to get output frame from pool\n"); + av_frame_free(&out); + return ret; + } + + AVD3D12VAFrame *out_d3d12_frame = (AVD3D12VAFrame *)out->data[0]; + + ID3D12Resource *input_resource = in_d3d12_frame->texture; + ID3D12Resource *output_resource = out_d3d12_frame->texture; + + /* Build past/future reference frame arrays from queue */ + ID3D12Resource *past_textures[MAX_REFERENCES]; + UINT past_subresources[MAX_REFERENCES]; + int actual_past = 0; + + ID3D12Resource *future_textures[MAX_REFERENCES]; + UINT future_subresources[MAX_REFERENCES]; + int actual_future = 0; + + if (queue_idx >= 0) { + /* Collect past reference textures from the queue, walking + * backwards from the current frame position. */ + for (i = 0; i < s->num_past_frames && (queue_idx - 1 - i) >= 0; i++) { + AVFrame *past_frame = s->frame_queue[queue_idx - 1 - i]; + if (past_frame) { + AVD3D12VAFrame *past_d3d12 = (AVD3D12VAFrame *)past_frame->data[0]; + past_textures[actual_past] = past_d3d12->texture; + past_subresources[actual_past] = 0; + actual_past++; + } + } + + /* Collect future reference textures from the queue, walking + * forwards from the current frame position. */ + for (i = 0; i < s->num_future_frames && (queue_idx + 1 + i) < s->queue_count; i++) { + AVFrame *future_frame = s->frame_queue[queue_idx + 1 + i]; + if (future_frame) { + AVD3D12VAFrame *future_d3d12 = (AVD3D12VAFrame *)future_frame->data[0]; + future_textures[actual_future] = future_d3d12->texture; + future_subresources[actual_future] = 0; + actual_future++; + } + } + + av_log(ctx, AV_LOG_DEBUG, + "Reference frames: past=%d/%d, future=%d/%d, queue_idx=%d\n", + actual_past, s->num_past_frames, actual_future, s->num_future_frames, + queue_idx); + } + + /* Wait for input frame's fence before accessing it */ + if (in_d3d12_frame->sync_ctx.fence && in_d3d12_frame->sync_ctx.fence_value > 0) { + UINT64 completed = ID3D12Fence_GetCompletedValue(in_d3d12_frame->sync_ctx.fence); + if (completed < in_d3d12_frame->sync_ctx.fence_value) { + hr = ID3D12CommandQueue_Wait(s->command_queue, in_d3d12_frame->sync_ctx.fence, + in_d3d12_frame->sync_ctx.fence_value); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to wait for input fence: HRESULT 0x%lX\n", hr); + ret = AVERROR_EXTERNAL; + goto fail; + } + } + } + + /* Wait for past and future reference frame fences before accessing them*/ + for (i = 0; i < actual_past; i++) { + AVFrame *past_frame = s->frame_queue[queue_idx - 1 - i]; + AVD3D12VAFrame *past_d3d12 = (AVD3D12VAFrame *)past_frame->data[0]; + if (past_d3d12->sync_ctx.fence && past_d3d12->sync_ctx.fence_value > 0) { + hr = ID3D12CommandQueue_Wait(s->command_queue, past_d3d12->sync_ctx.fence, + past_d3d12->sync_ctx.fence_value); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to wait for past frame fence: HRESULT 0x%lX\n", hr); + ret = AVERROR_EXTERNAL; + goto fail; + } + } + } + + for (i = 0; i < actual_future; i++) { + AVFrame *future_frame = s->frame_queue[queue_idx + 1 + i]; + AVD3D12VAFrame *future_d3d12 = (AVD3D12VAFrame *)future_frame->data[0]; + if (future_d3d12->sync_ctx.fence && future_d3d12->sync_ctx.fence_value > 0) { + hr = ID3D12CommandQueue_Wait(s->command_queue, future_d3d12->sync_ctx.fence, + future_d3d12->sync_ctx.fence_value); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to wait for future frame fence: HRESULT 0x%lX\n", hr); + ret = AVERROR_EXTERNAL; + goto fail; + } + } + } + + hr = ID3D12CommandAllocator_Reset(s->command_allocator); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to reset command allocator: HRESULT 0x%lX\n", hr); + ret = AVERROR_EXTERNAL; + goto fail; + } + + hr = ID3D12VideoProcessCommandList_Reset(s->command_list, s->command_allocator); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to reset command list: HRESULT 0x%lX\n", hr); + ret = AVERROR_EXTERNAL; + goto fail; + } + + /* Resource barriers: input + output + past refs + future refs */ + D3D12_RESOURCE_BARRIER barriers[2 + MAX_REFERENCES * 2]; + int num_barriers = 0; + + add_resource_barrier(barriers, &num_barriers, input_resource, + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VIDEO_PROCESS_READ); + add_resource_barrier(barriers, &num_barriers, output_resource, + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VIDEO_PROCESS_WRITE); + + for (i = 0; i < actual_past; i++) + add_resource_barrier(barriers, &num_barriers, past_textures[i], + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VIDEO_PROCESS_READ); + + for (i = 0; i < actual_future; i++) + add_resource_barrier(barriers, &num_barriers, future_textures[i], + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VIDEO_PROCESS_READ); + + ID3D12VideoProcessCommandList_ResourceBarrier(s->command_list, num_barriers, barriers); + + /* Setup input stream arguments */ + D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS input_args = {0}; + + input_args.InputStream[0].pTexture2D = input_resource; + + /* Populate reference set with past/future frames */ + input_args.InputStream[0].ReferenceSet.NumPastFrames = actual_past; + input_args.InputStream[0].ReferenceSet.ppPastFrames = actual_past > 0 ? past_textures : NULL; + input_args.InputStream[0].ReferenceSet.pPastSubresources = actual_past > 0 ? past_subresources : NULL; + input_args.InputStream[0].ReferenceSet.NumFutureFrames = actual_future; + input_args.InputStream[0].ReferenceSet.ppFutureFrames = actual_future > 0 ? future_textures : NULL; + input_args.InputStream[0].ReferenceSet.pFutureSubresources = actual_future > 0 ? future_subresources : NULL; + + input_args.Transform.SourceRectangle.right = s->width; + input_args.Transform.SourceRectangle.bottom = s->height; + input_args.Transform.DestinationRectangle.right = s->width; + input_args.Transform.DestinationRectangle.bottom = s->height; + input_args.Transform.Orientation = D3D12_VIDEO_PROCESS_ORIENTATION_DEFAULT; + + input_args.Flags = D3D12_VIDEO_PROCESS_INPUT_STREAM_FLAG_NONE; + + input_args.RateInfo.OutputIndex = field; + input_args.RateInfo.InputFrameOrField = 0; + + memset(input_args.FilterLevels, 0, sizeof(input_args.FilterLevels)); + + input_args.AlphaBlending.Enable = FALSE; + input_args.AlphaBlending.Alpha = 1.0f; + + /* Setup output stream arguments */ + D3D12_VIDEO_PROCESS_OUTPUT_STREAM_ARGUMENTS output_args = {0}; + + output_args.OutputStream[0].pTexture2D = output_resource; + output_args.TargetRectangle.right = s->width; + output_args.TargetRectangle.bottom = s->height; + + ID3D12VideoProcessCommandList_ProcessFrames( + s->command_list, + s->video_processor, + &output_args, + 1, + &input_args + ); + + /* Reverse barriers */ + for (i = 0; i < num_barriers; i++) { + FFSWAP(D3D12_RESOURCE_STATES, barriers[i].Transition.StateBefore, barriers[i].Transition.StateAfter); + } + ID3D12VideoProcessCommandList_ResourceBarrier(s->command_list, num_barriers, barriers); + + hr = ID3D12VideoProcessCommandList_Close(s->command_list); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to close command list: HRESULT 0x%lX\n", hr); + ret = AVERROR_EXTERNAL; + goto fail; + } + + ID3D12CommandList *cmd_lists[] = { (ID3D12CommandList *)s->command_list }; + ID3D12CommandQueue_ExecuteCommandLists(s->command_queue, 1, cmd_lists); + + hr = ID3D12CommandQueue_Signal(s->command_queue, s->fence, s->fence_value); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to signal fence: HRESULT 0x%lX\n", hr); + ret = AVERROR_EXTERNAL; + goto fail; + } + + out_d3d12_frame->sync_ctx.fence = s->fence; + out_d3d12_frame->sync_ctx.fence_value = s->fence_value; + ID3D12Fence_AddRef(s->fence); + + s->fence_value++; + + ret = av_frame_copy_props(out, input_frame); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to copy frame properties\n"); + goto fail; + } + + out->width = s->width; + out->height = s->height; + out->format = AV_PIX_FMT_D3D12; + out->flags &= ~AV_FRAME_FLAG_INTERLACED; + + /* Calculate output PTS for field rate output */ + if (s->field_rate == 2 && queue_idx >= 0) { + AVFrame *next_frame = (queue_idx + 1 < s->queue_count) ? + s->frame_queue[queue_idx + 1] : NULL; + + if (field == 0) { + out->pts = 2 * input_frame->pts; + } else if (s->eof || !next_frame) { + out->pts = 3 * input_frame->pts - s->prev_pts; + } else { + out->pts = input_frame->pts + next_frame->pts; + } + } + + av_log(ctx, AV_LOG_DEBUG, "Deinterlace output: %dx%d, pts=%"PRId64", field=%d\n", + out->width, out->height, out->pts, field); + + return ff_filter_frame(outlink, out); + +fail: + av_frame_free(&out); + return ret; +} + +static int deint_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + DeinterlaceD3D12Context *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + int ret = 0; + int field; + AVFrame *input_frame; + + /* Phase 4: EOF flush - process remaining frames in queue. + * Two sub-cases: + * a) Short stream: queue never filled, process all buffered frames + * b) Normal stream: process frames after current_frame_index + * (future refs and extra delay frames not yet output) */ + if (!in) { + if (s->eof && s->queue_count > 0) { + int flush_idx; + + if (!s->initial_fill_done) { + /* Short stream: queue never reached full depth. + * Process ALL buffered frames with whatever references are available. */ + flush_idx = 0; + av_log(ctx, AV_LOG_DEBUG, + "EOF flush (short stream): processing all %d buffered frames\n", + s->queue_count); + } else { + /* Normal stream: process remaining frames after the last + * normally-processed one (future refs, extra delay). */ + flush_idx = s->current_frame_index + 1; + av_log(ctx, AV_LOG_DEBUG, + "EOF flush: processing frames %d..%d in queue (count=%d)\n", + flush_idx, s->queue_count - 1, s->queue_count); + } + + while (flush_idx < s->queue_count) { + input_frame = s->frame_queue[flush_idx]; + if (input_frame) { + for (field = 0; field < s->field_rate; field++) { + ret = deint_d3d12_process_frame(ctx, outlink, input_frame, field, flush_idx); + if (ret < 0) + return ret; + } + s->prev_pts = input_frame->pts; + } + flush_idx++; + } + return AVERROR_EOF; + } + return AVERROR_EOF; + } + + if (!in->hw_frames_ctx) { + av_log(ctx, AV_LOG_ERROR, "No hardware frames context in input frame\n"); + av_frame_free(&in); + return AVERROR(EINVAL); + } + + av_log(ctx, AV_LOG_DEBUG, "Input frame: %dx%d, pts=%"PRId64", interlaced=%d\n", + in->width, in->height, in->pts, + !!(in->flags & AV_FRAME_FLAG_INTERLACED)); + + /* Initialize processor on first frame */ + if (!s->processor_configured) { + AVHWFramesContext *frames_ctx = (AVHWFramesContext *)in->hw_frames_ctx->data; + AVD3D12VAFramesContext *input_hwctx = (AVD3D12VAFramesContext *)frames_ctx->hwctx; + + s->width = frames_ctx->width; + s->height = frames_ctx->height; + s->input_format = input_hwctx->format; + + if (s->input_format == DXGI_FORMAT_UNKNOWN) { + switch (frames_ctx->sw_format) { + case AV_PIX_FMT_NV12: + s->input_format = DXGI_FORMAT_NV12; + break; + case AV_PIX_FMT_P010: + s->input_format = DXGI_FORMAT_P010; + break; + default: + av_log(ctx, AV_LOG_ERROR, "Unsupported input format: %s\n", + av_get_pix_fmt_name(frames_ctx->sw_format)); + av_frame_free(&in); + return AVERROR(EINVAL); + } + } + + int is_10bit = (s->input_format == DXGI_FORMAT_P010); + s->input_colorspace = get_dxgi_colorspace(in->colorspace, in->color_trc, is_10bit); + s->input_framerate = get_input_framerate(ctx, inlink, in); + + ret = deint_d3d12_configure_processor(s, ctx, in); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to configure processor\n"); + av_frame_free(&in); + return ret; + } + } + + /* Auto mode: pass through progressive frames by processing them as-is */ + if (s->auto_enable && !(in->flags & AV_FRAME_FLAG_INTERLACED)) { + av_log(ctx, AV_LOG_DEBUG, "Progressive frame, processing as pass-through\n"); + ret = deint_d3d12_process_frame(ctx, outlink, in, 0, -1); + av_frame_free(&in); + return ret; + } + + /* Queue management and frame processing. + * + * For bob mode, the hardware typically needs no reference frames + * (past=0, future=0), so queue_depth=1 and every input frame is + * processed immediately -- simple frame-in, frame-out. + * + * For custom (driver-defined) mode, the hardware uses temporal + * reference frames for higher-quality motion-adaptive deinterlacing. + * The queue possible holds past, current, and future reference frames: + * The queue is managed in four phases: + * 1. Filling: buffer frames until queue reaches queue_depth + * 2. Initial fill: queue just became full, process ALL buffered + * frames (0..current_frame_index) with degraded references + * for the earliest frames + * 3. Steady state: slide queue forward by one position per input, + * process the frame at current_frame_index with full references + * 4. EOF flush: process remaining frames after current_frame_index, + * or all buffered frames if the queue never filled (short stream) + * + * When queue_depth=1 (bob mode), phases 1, 2, and 4 are effectively + * skipped, and only the steady-state path executes. + */ + + if (s->queue_count < s->queue_depth) { + /* Phase 1: Filling - buffer incoming frames until we have enough + * past and future references to begin processing. */ + s->frame_queue[s->queue_count++] = in; + if (s->queue_count < s->queue_depth) + return 0; + + /* Phase 2: Initial fill complete - process all frames from the + * start of the queue through current_frame_index. The first + * frames will have fewer past references (graceful degradation), + * but the D3D12 video processor handles partial reference sets. */ + for (int i = 0; i <= s->current_frame_index; i++) { + input_frame = s->frame_queue[i]; + if (!input_frame) + continue; + for (field = 0; field < s->field_rate; field++) { + ret = deint_d3d12_process_frame(ctx, outlink, input_frame, field, i); + if (ret < 0) + return ret; + } + s->prev_pts = input_frame->pts; + } + s->initial_fill_done = 1; + return ret; + } + + /* Phase 3: Steady state - slide the queue forward by removing the + * oldest frame and appending the new one at the end. The frame at + * current_frame_index always has full past and future references. */ + av_frame_free(&s->frame_queue[0]); + for (int i = 0; i + 1 < s->queue_count; i++) + s->frame_queue[i] = s->frame_queue[i + 1]; + s->frame_queue[s->queue_count - 1] = in; + + input_frame = s->frame_queue[s->current_frame_index]; + if (!input_frame) + return 0; + + for (field = 0; field < s->field_rate; field++) { + ret = deint_d3d12_process_frame(ctx, outlink, input_frame, field, s->current_frame_index); + if (ret < 0) + break; + } + + s->prev_pts = input_frame->pts; + + return ret; +} + +static int deint_d3d12_request_frame(AVFilterLink *link) +{ + AVFilterContext *ctx = link->src; + DeinterlaceD3D12Context *s = ctx->priv; + int ret; + + if (s->eof) + return AVERROR_EOF; + + ret = ff_request_frame(ctx->inputs[0]); + if (ret == AVERROR_EOF && s->queue_count > 0) { + s->eof = 1; + /* Flush remaining frames in queue (future frames, extra delay, + * or short stream where queue never fully filled) */ + return deint_d3d12_filter_frame(ctx->inputs[0], NULL); + } + + return ret; +} + +static int deint_d3d12_config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + DeinterlaceD3D12Context *s = ctx->priv; + AVFilterLink *inlink = ctx->inputs[0]; + FilterLink *inl = ff_filter_link(inlink); + FilterLink *outl = ff_filter_link(outlink); + int ret; + + release_d3d12_resources(s); + deint_d3d12_clear_queue(s); + + av_buffer_unref(&s->hw_frames_ctx_out); + av_buffer_unref(&s->hw_device_ctx); + + s->processor_configured = 0; + + outlink->w = inlink->w; + outlink->h = inlink->h; + s->width = inlink->w; + s->height = inlink->h; + + /* Adjust time base and frame rate for field rate output */ + outlink->time_base = av_mul_q(inlink->time_base, (AVRational){ 1, s->field_rate }); + outl->frame_rate = av_mul_q(inl->frame_rate, (AVRational){ s->field_rate, 1 }); + + if (!inl->hw_frames_ctx) { + av_log(ctx, AV_LOG_ERROR, "No hw_frames_ctx available on input link\n"); + return AVERROR(EINVAL); + } + + AVHWFramesContext *in_frames_ctx = (AVHWFramesContext *)inl->hw_frames_ctx->data; + s->hw_device_ctx = av_buffer_ref(in_frames_ctx->device_ref); + if (!s->hw_device_ctx) { + av_log(ctx, AV_LOG_ERROR, "Failed to reference device context\n"); + return AVERROR(ENOMEM); + } + + s->hw_frames_ctx_out = av_hwframe_ctx_alloc(s->hw_device_ctx); + if (!s->hw_frames_ctx_out) + return AVERROR(ENOMEM); + + AVHWFramesContext *frames_ctx = (AVHWFramesContext *)s->hw_frames_ctx_out->data; + + frames_ctx->format = AV_PIX_FMT_D3D12; + frames_ctx->sw_format = in_frames_ctx->sw_format; + frames_ctx->width = s->width; + frames_ctx->height = s->height; + frames_ctx->initial_pool_size = 10; + + if (ctx->extra_hw_frames > 0) + frames_ctx->initial_pool_size += ctx->extra_hw_frames; + + AVD3D12VAFramesContext *frames_hwctx = frames_ctx->hwctx; + AVD3D12VAFramesContext *in_frames_hwctx = in_frames_ctx->hwctx; + + frames_hwctx->format = in_frames_hwctx->format; + frames_hwctx->resource_flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + frames_hwctx->heap_flags = D3D12_HEAP_FLAG_NONE; + + ret = av_hwframe_ctx_init(s->hw_frames_ctx_out); + if (ret < 0) { + av_buffer_unref(&s->hw_frames_ctx_out); + return ret; + } + + outl->hw_frames_ctx = av_buffer_ref(s->hw_frames_ctx_out); + if (!outl->hw_frames_ctx) + return AVERROR(ENOMEM); + + av_log(ctx, AV_LOG_VERBOSE, "D3D12 deinterlace config: %dx%d, field_rate=%d\n", + outlink->w, outlink->h, s->field_rate); + + return 0; +} + +static av_cold void deint_d3d12_uninit(AVFilterContext *ctx) +{ + DeinterlaceD3D12Context *s = ctx->priv; + + release_d3d12_resources(s); + deint_d3d12_clear_queue(s); + + av_buffer_unref(&s->hw_frames_ctx_out); + av_buffer_unref(&s->hw_device_ctx); +} + +static const AVFilterPad deint_d3d12_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = deint_d3d12_filter_frame, + }, +}; + +static const AVFilterPad deint_d3d12_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .request_frame = deint_d3d12_request_frame, + .config_props = deint_d3d12_config_output, + }, +}; + +#define OFFSET(x) offsetof(DeinterlaceD3D12Context, x) +#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM) + +static const AVOption deinterlace_d3d12_options[] = { + { "mode", "Deinterlacing mode", + OFFSET(mode), AV_OPT_TYPE_INT, { .i64 = DEINT_D3D12_MODE_DEFAULT }, + DEINT_D3D12_MODE_DEFAULT, DEINT_D3D12_MODE_CUSTOM, FLAGS, .unit = "mode" }, + { "default", "Use best available deinterlacing mode", + 0, AV_OPT_TYPE_CONST, { .i64 = DEINT_D3D12_MODE_DEFAULT }, 0, 0, FLAGS, .unit = "mode" }, + { "bob", "Bob deinterlacing (simple field interpolation)", + 0, AV_OPT_TYPE_CONST, { .i64 = DEINT_D3D12_MODE_BOB }, 0, 0, FLAGS, .unit = "mode" }, + { "custom", "Driver-defined advanced deinterlacing", + 0, AV_OPT_TYPE_CONST, { .i64 = DEINT_D3D12_MODE_CUSTOM }, 0, 0, FLAGS, .unit = "mode" }, + + { "rate", "Generate output at frame rate or field rate", + OFFSET(field_rate), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, 2, FLAGS, .unit = "rate" }, + { "frame", "Output at frame rate (one frame for each field-pair)", + 0, AV_OPT_TYPE_CONST, { .i64 = 1 }, 0, 0, FLAGS, .unit = "rate" }, + { "field", "Output at field rate (one frame for each field)", + 0, AV_OPT_TYPE_CONST, { .i64 = 2 }, 0, 0, FLAGS, .unit = "rate" }, + + { "auto", "Only deinterlace interlaced frames, pass through progressive", + OFFSET(auto_enable), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, + + { NULL } +}; + +AVFILTER_DEFINE_CLASS(deinterlace_d3d12); + +const FFFilter ff_vf_deinterlace_d3d12 = { + .p.name = "deinterlace_d3d12", + .p.description = NULL_IF_CONFIG_SMALL("Deinterlacing using Direct3D12 Video Processor"), + .priv_size = sizeof(DeinterlaceD3D12Context), + .p.priv_class = &deinterlace_d3d12_class, + .init = deint_d3d12_init, + .uninit = deint_d3d12_uninit, + FILTER_INPUTS(deint_d3d12_inputs), + FILTER_OUTPUTS(deint_d3d12_outputs), + FILTER_SINGLE_PIXFMT(AV_PIX_FMT_D3D12), + .p.flags = AVFILTER_FLAG_HWDEVICE, + .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, +}; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org