Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PR] avfilter: add d3d12 deinterlace filter `deinterlace_d3d12` (PR #21778)
@ 2026-02-17 23:11 Steven Xiao via ffmpeg-devel
  0 siblings, 0 replies; only message in thread
From: Steven Xiao via ffmpeg-devel @ 2026-02-17 23:11 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Steven Xiao

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 <steven.xiao@amd.com>


>From 7d1e62021e1faf495c8ddd70b1bff280b391273b Mon Sep 17 00:00:00 2001
From: stevxiao <steven.xiao@amd.com>
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 <steven.xiao@amd.com>
---
 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 <next>:
 - 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

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-02-17 23:11 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-17 23:11 [FFmpeg-devel] [PR] avfilter: add d3d12 deinterlace filter `deinterlace_d3d12` (PR #21778) Steven Xiao via ffmpeg-devel

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git