PR #25821 opened by Steven Xiao (younengxiao) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25821 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25821.patch
scale_d3d12, deinterlace_d3d12 and mestimate_d3d12 each create their own command queues, command allocators and fences, and tear down their GPU work on their own. These copies are no longer in sync: the frame synchronization, input frame reference and teardown fixes made in scale_d3d12 never reached deinterlace_d3d12, and other upcoming D3D12 filters would add more copies. Add the scale_d3d12 implementation of this code as shared helpers, generalized so that it is not tied to the video processor, in two layers: - d3d12_filter.c/h: pixel format and color space mapping, fence waits, resource barriers, output frame pool setup, and D3D12CommandContext, which wraps a DIRECT, VIDEO_PROCESS or VIDEO_ENCODE command queue with its command list, command allocator pool and fence, and handles frame synchronization, input frame references and teardown. - d3d12_vpp.c/h: D3D12VPPContext, which adds the video device and the video processor on top of a VIDEO_PROCESS command context. For scale_d3d12 specifically: Replace the filter's own command queue, command list, command allocator pool, fence handling, GPU teardown, format table, color space mapping and output frame pool setup with the helpers from d3d12_filter.c and d3d12_vpp.c. >From 5f458f6f60fa3e3dabdd952bdce0bd49740c30b5 Mon Sep 17 00:00:00 2001 From: Steven Xiao <[email protected]> Date: Fri, 6 Nov 2026 18:31:31 -0500 Subject: [PATCH 1/2] avfilter: add shared helpers for the D3D12 filters scale_d3d12, deinterlace_d3d12 and mestimate_d3d12 each create their own command queues, command allocators and fences, and tear down their GPU work on their own. These copies are no longer in sync: the frame synchronization, input frame reference and teardown fixes made in scale_d3d12 never reached deinterlace_d3d12, and other upcoming D3D12 filters would add more copies. Add the scale_d3d12 implementation of this code as shared helpers, generalized so that it is not tied to the video processor, in two layers: - d3d12_filter.c/h: pixel format and color space mapping, fence waits, resource barriers, output frame pool setup, and D3D12CommandContext, which wraps a DIRECT, VIDEO_PROCESS or VIDEO_ENCODE command queue with its command list, command allocator pool and fence, and handles frame synchronization, input frame references and teardown. - d3d12_vpp.c/h: D3D12VPPContext, which adds the video device and the video processor on top of a VIDEO_PROCESS command context. Signed-off-by: younengxiao <[email protected]> --- libavfilter/Makefile | 1 + libavfilter/d3d12_filter.c | 1146 ++++++++++++++++++++++++++++++++++++ libavfilter/d3d12_filter.h | 195 ++++++ libavfilter/d3d12_vpp.c | 182 ++++++ libavfilter/d3d12_vpp.h | 89 +++ 5 files changed, 1613 insertions(+) create mode 100644 libavfilter/d3d12_filter.c create mode 100644 libavfilter/d3d12_filter.h create mode 100644 libavfilter/d3d12_vpp.c create mode 100644 libavfilter/d3d12_vpp.h diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 732332d5b2b..1b5c11fb4c2 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -681,6 +681,7 @@ SKIPHEADERS-$(CONFIG_LCMS2) += fflcms2.h SKIPHEADERS-$(CONFIG_LIBVIDSTAB) += vidstabutils.h SKIPHEADERS-$(CONFIG_AMF) += vf_amf_common.h +SKIPHEADERS-$(CONFIG_D3D12VA) += d3d12_filter.h d3d12_vpp.h SKIPHEADERS-$(CONFIG_QSVVPP) += qsvvpp.h stack_internal.h SKIPHEADERS-$(CONFIG_OPENCL) += opencl.h SKIPHEADERS-$(CONFIG_VAAPI) += vaapi_vpp.h stack_internal.h diff --git a/libavfilter/d3d12_filter.c b/libavfilter/d3d12_filter.c new file mode 100644 index 00000000000..6401f24a8d4 --- /dev/null +++ b/libavfilter/d3d12_filter.c @@ -0,0 +1,1146 @@ +/* + * D3D12VA filter helpers + * + * 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 <stdint.h> +#include <string.h> + +#include "libavutil/avassert.h" +#include "libavutil/fifo.h" +#include "libavutil/frame.h" +#include "libavutil/hwcontext.h" +#include "libavutil/hwcontext_d3d12va.h" +#include "libavutil/pixdesc.h" + +#include "d3d12_filter.h" +#include "filters.h" + +typedef struct D3D12CommandAllocator { + ID3D12CommandAllocator *command_allocator; + UINT64 fence_value; + AVFrame *input_refs[D3D12_FILTER_MAX_INPUTS]; + int nb_input_refs; +} D3D12CommandAllocator; + +static const D3D12FilterFormat d3d12_filter_formats[] = { + { AV_PIX_FMT_NV12, DXGI_FORMAT_NV12, 0 }, + { AV_PIX_FMT_P010, DXGI_FORMAT_P010, 0 }, + { AV_PIX_FMT_P012, DXGI_FORMAT_P016, 0 }, + { AV_PIX_FMT_BGRA, DXGI_FORMAT_B8G8R8A8_UNORM, 1 }, + { AV_PIX_FMT_RGBA, DXGI_FORMAT_R8G8B8A8_UNORM, 1 }, + { AV_PIX_FMT_X2BGR10, DXGI_FORMAT_R10G10B10A2_UNORM, 1 }, + { AV_PIX_FMT_RGBAF16, DXGI_FORMAT_R16G16B16A16_FLOAT, 1 }, +}; + +const D3D12FilterFormat *ff_d3d12_filter_format_from_pix_fmt(enum AVPixelFormat fmt) +{ + for (int i = 0; i < FF_ARRAY_ELEMS(d3d12_filter_formats); i++) + if (d3d12_filter_formats[i].pix_fmt == fmt) + return &d3d12_filter_formats[i]; + + return NULL; +} + +const D3D12FilterFormat *ff_d3d12_filter_format_from_dxgi(DXGI_FORMAT fmt) +{ + for (int i = 0; i < FF_ARRAY_ELEMS(d3d12_filter_formats); i++) + if (d3d12_filter_formats[i].dxgi_format == fmt) + return &d3d12_filter_formats[i]; + + return NULL; +} + +const D3D12FilterFormat *ff_d3d12_filter_format_iterate(void **opaque) +{ + uintptr_t i = (uintptr_t)*opaque; + + if (i >= FF_ARRAY_ELEMS(d3d12_filter_formats)) + return NULL; + + *opaque = (void *)(i + 1); + + return &d3d12_filter_formats[i]; +} + +static int d3d12_filter_known_matrix(enum AVColorSpace spc) +{ + switch (spc) { + case AVCOL_SPC_UNSPECIFIED: + case AVCOL_SPC_BT709: + case AVCOL_SPC_BT470BG: + case AVCOL_SPC_SMPTE170M: + case AVCOL_SPC_BT2020_NCL: + case AVCOL_SPC_BT2020_CL: + return 1; + + default: + return 0; + } +} + +static int d3d12_filter_known_sdr_trc(enum AVColorTransferCharacteristic trc) +{ + switch (trc) { + case AVCOL_TRC_UNSPECIFIED: + case AVCOL_TRC_BT709: + case AVCOL_TRC_GAMMA22: + case AVCOL_TRC_GAMMA28: + case AVCOL_TRC_SMPTE170M: + case AVCOL_TRC_SMPTE240M: + case AVCOL_TRC_IEC61966_2_1: + case AVCOL_TRC_BT2020_10: + case AVCOL_TRC_BT2020_12: + return 1; + + default: + return 0; + } +} + +static int d3d12_filter_reconcile_primaries(AVFilterContext *ctx, int is_output, + enum AVColorPrimaries *primaries, + enum AVColorPrimaries expected_primaries, + const char *space_description) +{ + if (*primaries != AVCOL_PRI_UNSPECIFIED && *primaries != expected_primaries) { + const char *old_name = av_color_primaries_name(*primaries); + const char *new_name = av_color_primaries_name(expected_primaries); + if (is_output) { + av_log(ctx, AV_LOG_WARNING, + "%s forces fixed primaries; retagging output from " + "%s to %s\n", + space_description, + old_name ? old_name : "unknown", new_name ? new_name : "unknown"); + } else { + av_log(ctx, AV_LOG_ERROR, + "%s forces fixed primaries, which disagrees with " + "this input's %s primaries tag\n", + space_description, old_name ? old_name : "unknown"); + + return AVERROR(EINVAL); + } + } + + *primaries = expected_primaries; + return 0; +} + +static int d3d12_filter_reject_unrepresentable_nonleft(AVFilterContext *ctx, int is_output, + enum AVChromaLocation chroma_loc) +{ + if (chroma_loc == AVCHROMA_LOC_UNSPECIFIED || chroma_loc == AVCHROMA_LOC_LEFT) + return 0; + + if (is_output) { + av_log(ctx, AV_LOG_WARNING, + "This DXGI color space has no chroma entry other than " + "left-sited; the output will actually be left-sited\n"); + + return 0; + } + + av_log(ctx, AV_LOG_ERROR, + "This DXGI color space has no chroma entry other than " + "left-sited, but this input is tagged with a different siting\n"); + + return AVERROR(EINVAL); +} + +static int d3d12_filter_reject_unrepresentable_left(AVFilterContext *ctx, int is_output, + enum AVChromaLocation chroma_loc) +{ + if (chroma_loc == AVCHROMA_LOC_UNSPECIFIED || chroma_loc == AVCHROMA_LOC_TOPLEFT) + return 0; + + if (is_output) { + av_log(ctx, AV_LOG_WARNING, + "HLG has no DXGI entry with any chroma siting other than " + "top-left; the output will actually be top-left-sited\n"); + + return 0; + } + + av_log(ctx, AV_LOG_ERROR, + "HLG has no DXGI entry with any chroma siting other than " + "top-left, but this input is tagged with a different siting\n"); + + return AVERROR(EINVAL); +} + +static int d3d12_filter_reject_unrepresentable_siting_2020(AVFilterContext *ctx, int is_output, + enum AVChromaLocation chroma_loc) +{ + if (chroma_loc == AVCHROMA_LOC_UNSPECIFIED || + chroma_loc == AVCHROMA_LOC_TOPLEFT || + chroma_loc == AVCHROMA_LOC_LEFT) + return 0; + + if (is_output) { + av_log(ctx, AV_LOG_WARNING, + "DXGI BT.2020 only supports top-left/left chroma " + "siting; output will use one of those instead\n"); + + return 0; + } + + av_log(ctx, AV_LOG_ERROR, + "DXGI BT.2020 only supports top-left/left chroma siting, " + "but this input is tagged with a different siting\n"); + + return AVERROR(EINVAL); +} + +int ff_d3d12_filter_get_colorspace(AVFilterContext *ctx, int is_rgb, int is_output, + enum AVColorSpace *spc, + enum AVColorRange *range, + enum AVColorPrimaries *primaries, + enum AVColorTransferCharacteristic *trc, + enum AVChromaLocation chroma_loc, + enum AVColorPrimaries input_601_hint, + DXGI_COLOR_SPACE_TYPE *out_space) +{ + int full_range = (*range == AVCOL_RANGE_JPEG); + int ret; + + if (!is_rgb && *spc == AVCOL_SPC_BT2020_CL) { + if (is_output) { + av_log(ctx, AV_LOG_WARNING, + "BT.2020 constant-luminance has no DXGI color space; " + "retagging output as bt2020nc\n"); + *spc = AVCOL_SPC_BT2020_NCL; + } else { + av_log(ctx, AV_LOG_ERROR, + "BT.2020 constant-luminance matrix has no DXGI color " + "space; cannot process this input without misinterpreting\n"); + + return AVERROR(EINVAL); + } + } + + if (!is_rgb && !d3d12_filter_known_matrix(*spc)) { + const char *name = av_color_space_name(*spc); + av_log(ctx, AV_LOG_ERROR, + "%s matrix has no DXGI YCbCr color space; only BT.601, " + "BT.709 or BT.2020 are supported\n", name ? name : "this"); + + return AVERROR(EINVAL); + } + + if (!is_rgb && *trc != AVCOL_TRC_SMPTE2084 && *trc != AVCOL_TRC_ARIB_STD_B67 && + !d3d12_filter_known_sdr_trc(*trc)) { + const char *name = av_color_transfer_name(*trc); + av_log(ctx, AV_LOG_ERROR, + "%s transfer has no DXGI YCbCr color space; only SDR " + "gamma, smpte2084 or HLG (arib-std-b67) are " + "supported\n", + name ? name : "this"); + + return AVERROR(EINVAL); + } + + int primaries_is_2020 = (*primaries == AVCOL_PRI_BT2020); + int is_2020 = (*spc == AVCOL_SPC_BT2020_NCL || *spc == AVCOL_SPC_BT2020_CL || + (*spc == AVCOL_SPC_UNSPECIFIED && primaries_is_2020)); + int is_601 = (*spc == AVCOL_SPC_BT470BG || *spc == AVCOL_SPC_SMPTE170M || + (*spc == AVCOL_SPC_UNSPECIFIED && + (*primaries == AVCOL_PRI_BT470BG || *primaries == AVCOL_PRI_SMPTE170M))); + + if (is_rgb) { + enum AVColorPrimaries expected_primaries; + + switch (*trc) { + case AVCOL_TRC_SMPTE2084: + *out_space = full_range ? DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 + : DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020; + expected_primaries = AVCOL_PRI_BT2020; + break; + + case AVCOL_TRC_ARIB_STD_B67: + if (is_output) + av_log(ctx, AV_LOG_ERROR, + "HLG has no RGB DXGI color space; use a YUV " + "output format or a supported out_color_transfer\n"); + else + av_log(ctx, AV_LOG_ERROR, + "HLG transfer characteristic has no RGB DXGI color " + "space; this RGB input cannot be processed\n"); + return AVERROR(EINVAL); + + case AVCOL_TRC_LINEAR: + if (!full_range) { + if (is_output) + av_log(ctx, AV_LOG_ERROR, + "Studio range linear RGB has no DXGI color " + "space; use out_range=full, or a non-linear " + "out_color_transfer (e.g. bt709)\n"); + else + av_log(ctx, AV_LOG_ERROR, + "Studio range linear RGB has no DXGI color " + "space; this input cannot be processed\n"); + + return AVERROR(EINVAL); + } + *out_space = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; + expected_primaries = AVCOL_PRI_BT709; + break; + + default: + if (!d3d12_filter_known_sdr_trc(*trc)) { + const char *name = av_color_transfer_name(*trc); + av_log(ctx, AV_LOG_ERROR, + "%s transfer has no RGB DXGI color space; only " + "SDR gamma, linear or smpte2084 are " + "supported\n", name ? name : "this"); + + return AVERROR(EINVAL); + } + if (primaries_is_2020) { + *out_space = full_range ? DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020 + : DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020; + expected_primaries = AVCOL_PRI_BT2020; + } else { + *out_space = full_range ? DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 + : DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709; + expected_primaries = AVCOL_PRI_BT709; + } + break; + } + + return d3d12_filter_reconcile_primaries(ctx, is_output, primaries, expected_primaries, + "this RGB transfer characteristic"); + } + + /* YCbCr family. */ + if (is_2020) { + int want_topleft = (chroma_loc == AVCHROMA_LOC_TOPLEFT); + ret = d3d12_filter_reject_unrepresentable_siting_2020(ctx, is_output, chroma_loc); + if (ret < 0) + return ret; + + if (*trc == AVCOL_TRC_SMPTE2084) { + if (full_range) { + av_log(ctx, AV_LOG_ERROR, + "No full-range smpte2084 YCbCr DXGI color space " + "exists; cannot process this %s\n", + is_output ? "output request" : "input"); + + return AVERROR(EINVAL); + } + *out_space = want_topleft ? DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020 + : DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020; + } else if (*trc == AVCOL_TRC_ARIB_STD_B67) { + ret = d3d12_filter_reject_unrepresentable_left(ctx, is_output, chroma_loc); + if (ret < 0) + return ret; + *out_space = full_range ? DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020 + : DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020; + } else { + if (want_topleft && full_range) { + if (!is_output) { + av_log(ctx, AV_LOG_ERROR, + "Full-range BT.2020 SDR has no top-left-sited " + "DXGI YCbCr color space\n"); + + return AVERROR(EINVAL); + } + av_log(ctx, AV_LOG_WARNING, + "Full-range BT.2020 SDR has no top-left-sited DXGI " + "YCbCr color space; falling back to left-sited\n"); + want_topleft = 0; + } + if (want_topleft) + *out_space = DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020; + else + *out_space = full_range ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020 + : DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020; + } + + ret = d3d12_filter_reconcile_primaries(ctx, is_output, primaries, AVCOL_PRI_BT2020, + "a BT.2020 matrix"); + if (ret < 0) + return ret; + } else if (is_601) { + if (*trc == AVCOL_TRC_SMPTE2084 || *trc == AVCOL_TRC_ARIB_STD_B67) { + if (is_output) + av_log(ctx, AV_LOG_ERROR, + "%s transfer has no BT.601 DXGI color space; use " + "out_color_matrix=bt2020nc or an SDR transfer\n", + *trc == AVCOL_TRC_SMPTE2084 ? "smpte2084" : "HLG"); + else + av_log(ctx, AV_LOG_ERROR, + "%s transfer has no BT.601 DXGI color space; this " + "input cannot be processed.\n", + *trc == AVCOL_TRC_SMPTE2084 ? "smpte2084" : "HLG"); + + return AVERROR(EINVAL); + } + ret = d3d12_filter_reject_unrepresentable_nonleft(ctx, is_output, chroma_loc); + if (ret < 0) + return ret; + *out_space = full_range ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601 + : DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601; + + enum AVColorPrimaries this_601_primaries = + *spc == AVCOL_SPC_BT470BG ? AVCOL_PRI_BT470BG : + *spc == AVCOL_SPC_SMPTE170M ? AVCOL_PRI_SMPTE170M : + *primaries; + + if (is_output && input_601_hint != AVCOL_PRI_UNSPECIFIED) { + if (*primaries != AVCOL_PRI_UNSPECIFIED && *primaries != input_601_hint) { + const char *in_name = av_color_primaries_name(input_601_hint); + const char *out_name = av_color_primaries_name(*primaries); + av_log(ctx, AV_LOG_ERROR, + "DXGI's BT.601 color spaces cannot distinguish " + "PAL/NTSC primaries (%s input, %s output)\n", + in_name ? in_name : "unknown", out_name ? out_name : "unknown"); + + return AVERROR(EINVAL); + } + if (*primaries != input_601_hint) { + const char *new_name = av_color_primaries_name(input_601_hint); + av_log(ctx, AV_LOG_WARNING, + "BT.601 cannot change primaries; tagging output " + "as %s\n", + new_name ? new_name : "unknown"); + } + *primaries = input_601_hint; + } else if (!is_output) { + if (*primaries != AVCOL_PRI_UNSPECIFIED && + *primaries != AVCOL_PRI_BT470BG && *primaries != AVCOL_PRI_SMPTE170M) { + const char *old_name = av_color_primaries_name(*primaries); + av_log(ctx, AV_LOG_ERROR, + "BT.601 only represents PAL/NTSC primaries, " + "which disagrees with this input's %s tag\n", + old_name ? old_name : "unknown"); + + return AVERROR(EINVAL); + } + if (*primaries == AVCOL_PRI_UNSPECIFIED) + *primaries = this_601_primaries; + } else { + ret = d3d12_filter_reconcile_primaries(ctx, is_output, primaries, this_601_primaries, + "this BT.601 matrix"); + if (ret < 0) + return ret; + } + } else { + /* Same gap as the BT.601 case above: no smpte2084/HLG DXGI color + * space exists paired with a BT.709 matrix either. */ + if (*trc == AVCOL_TRC_SMPTE2084 || *trc == AVCOL_TRC_ARIB_STD_B67) { + if (is_output) + av_log(ctx, AV_LOG_ERROR, + "%s has no BT.709 DXGI space; use " + "out_color_matrix=bt2020nc or SDR\n", + *trc == AVCOL_TRC_SMPTE2084 ? "smpte2084" : "HLG"); + else + av_log(ctx, AV_LOG_ERROR, + "%s has no BT.709 DXGI space; retag/convert to " + "BT.2020 or SDR before %s\n", + *trc == AVCOL_TRC_SMPTE2084 ? "smpte2084" : "HLG", + ctx->filter->name); + + return AVERROR(EINVAL); + } + ret = d3d12_filter_reject_unrepresentable_nonleft(ctx, is_output, chroma_loc); + if (ret < 0) + return ret; + *out_space = full_range ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709 + : DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709; ///< Default to Rec.709 + + ret = d3d12_filter_reconcile_primaries(ctx, is_output, primaries, AVCOL_PRI_BT709, + "the default BT.709 matrix"); + if (ret < 0) + return ret; + } + + return 0; +} + +enum AVChromaLocation ff_d3d12_filter_chroma_location(DXGI_COLOR_SPACE_TYPE space) +{ + switch (space) { + case DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020: + case DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020: + case DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020: + return AVCHROMA_LOC_TOPLEFT; + + default: + return AVCHROMA_LOC_LEFT; + } +} + +AVRational ff_d3d12_filter_get_input_framerate(AVFilterContext *ctx, AVFilterLink *inlink, + const AVFrame *in) +{ + AVRational framerate = {0, 0}; + FilterLink *inl = ff_filter_link(inlink); + + if (inl->frame_rate.num > 0 && inl->frame_rate.den > 0) { + framerate = inl->frame_rate; + } else 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 { + framerate.num = 30; + framerate.den = 1; + av_log(ctx, AV_LOG_WARNING, "Input framerate not determinable, defaulting to 30fps\n"); + } + + return framerate; +} + +int ff_d3d12_filter_wait_fence(ID3D12Fence *fence, UINT64 target, + HANDLE fence_event, DWORD timeout_ms) +{ + int has_deadline = (timeout_ms != INFINITE); + ULONGLONG deadline = 0; + + if (ID3D12Fence_GetCompletedValue(fence) >= target) + return 1; + + if (FAILED(ID3D12Fence_SetEventOnCompletion(fence, target, fence_event))) + return 0; + + if (has_deadline) + deadline = GetTickCount64() + timeout_ms; + + for (;;) { + DWORD wait_result = WaitForSingleObject(fence_event, timeout_ms); + + if (wait_result == WAIT_OBJECT_0) { + if (ID3D12Fence_GetCompletedValue(fence) >= target) + return 1; + if (has_deadline) { + ULONGLONG now = GetTickCount64(); + if (now >= deadline) + return 0; + timeout_ms = (DWORD)(deadline - now); + } + continue; + } + + return 0; + } +} + +void ff_d3d12_filter_add_frame_barriers(D3D12_RESOURCE_BARRIER *barriers, int *nb_barriers, + const AVD3D12VAFrame *frame, int nb_planes, + D3D12_RESOURCE_STATES state_before, + D3D12_RESOURCE_STATES state_after) +{ + ID3D12Resource *resource = frame->texture; + D3D12_RESOURCE_DESC desc; + + resource->lpVtbl->GetDesc(resource, &desc); + + for (int p = 0; p < nb_planes; p++) { + D3D12_RESOURCE_BARRIER b = { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + .Transition = { + .pResource = resource, + .Subresource = frame->subresource_index + p * desc.DepthOrArraySize, + .StateBefore = state_before, + .StateAfter = state_after + } + }; + barriers[(*nb_barriers)++] = b; + } +} + +int ff_d3d12_filter_init_output_frames(AVFilterContext *ctx, AVBufferRef *device_ref, + enum AVPixelFormat sw_format, int width, int height, + int initial_pool_size, AVBufferRef **frames_ref) +{ + const D3D12FilterFormat *map = ff_d3d12_filter_format_from_pix_fmt(sw_format); + AVHWFramesContext *frames_ctx; + AVD3D12VAFramesContext *frames_hwctx; + AVBufferRef *ref; + int ret; + + if (!map) { + av_log(ctx, AV_LOG_ERROR, "Unsupported output format: %s\n", + av_get_pix_fmt_name(sw_format)); + return AVERROR(EINVAL); + } + + ref = av_hwframe_ctx_alloc(device_ref); + if (!ref) + return AVERROR(ENOMEM); + + frames_ctx = (AVHWFramesContext *)ref->data; + frames_ctx->format = AV_PIX_FMT_D3D12; + frames_ctx->sw_format = sw_format; + frames_ctx->width = width; + frames_ctx->height = height; + frames_ctx->initial_pool_size = initial_pool_size; + if (ctx->extra_hw_frames > 0) + frames_ctx->initial_pool_size += ctx->extra_hw_frames; + + frames_hwctx = frames_ctx->hwctx; + frames_hwctx->format = map->dxgi_format; + frames_hwctx->resource_flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + frames_hwctx->heap_flags = D3D12_HEAP_FLAG_NONE; + + ret = av_hwframe_ctx_init(ref); + if (ret < 0) { + av_buffer_unref(&ref); + return ret; + } + + *frames_ref = ref; + + return 0; +} + +static ID3D12CommandList *d3d12_cmd_get_list(const D3D12CommandContext *cmd) +{ + switch (cmd->type) { + case D3D12_COMMAND_LIST_TYPE_DIRECT: + return (ID3D12CommandList *)cmd->list.graphics; + case D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS: + return (ID3D12CommandList *)cmd->list.video_process; + case D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE: + return (ID3D12CommandList *)cmd->list.video_encode; + default: + return NULL; + } +} + +static HRESULT d3d12_cmd_reset_list(D3D12CommandContext *cmd, ID3D12CommandAllocator *allocator) +{ + switch (cmd->type) { + case D3D12_COMMAND_LIST_TYPE_DIRECT: + return ID3D12GraphicsCommandList_Reset(cmd->list.graphics, allocator, NULL); + case D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS: + return ID3D12VideoProcessCommandList_Reset(cmd->list.video_process, allocator); + case D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE: + return ID3D12VideoEncodeCommandList_Reset(cmd->list.video_encode, allocator); + default: + return E_INVALIDARG; + } +} + +static HRESULT d3d12_cmd_close_list(D3D12CommandContext *cmd) +{ + switch (cmd->type) { + case D3D12_COMMAND_LIST_TYPE_DIRECT: + return ID3D12GraphicsCommandList_Close(cmd->list.graphics); + case D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS: + return ID3D12VideoProcessCommandList_Close(cmd->list.video_process); + case D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE: + return ID3D12VideoEncodeCommandList_Close(cmd->list.video_encode); + default: + return E_INVALIDARG; + } +} + +static void d3d12_cmd_free_input_refs(D3D12CommandAllocator *allocator) +{ + for (int i = 0; i < allocator->nb_input_refs; i++) + av_frame_free(&allocator->input_refs[i]); + allocator->nb_input_refs = 0; +} + +static int d3d12_cmd_get_valid_command_allocator(D3D12CommandContext *cmd, AVFilterContext *ctx, + ID3D12CommandAllocator **ppAllocator) +{ + HRESULT hr; + D3D12CommandAllocator allocator; + + if (av_fifo_peek(cmd->allocator_queue, &allocator, 1, 0) >= 0) { + UINT64 completed = ID3D12Fence_GetCompletedValue(cmd->fence); + if (completed >= allocator.fence_value) { + *ppAllocator = allocator.command_allocator; + av_fifo_read(cmd->allocator_queue, &allocator, 1); + d3d12_cmd_free_input_refs(&allocator); + return 0; + } + } + + hr = ID3D12Device_CreateCommandAllocator(cmd->device, cmd->type, + &IID_ID3D12CommandAllocator, (void **)ppAllocator); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create a new command allocator: HRESULT 0x%lX\n", hr); + return AVERROR_EXTERNAL; + } + + return 0; +} + +/* Takes ownership of the command allocator and input references. */ +static int d3d12_cmd_discard_command_allocator(D3D12CommandContext *cmd, AVFilterContext *ctx, + D3D12CommandAllocator *allocator) +{ + int ret = av_fifo_write(cmd->allocator_queue, allocator, 1); + + if (ret < 0) { + int confirmed_done = cmd->fence && cmd->fence_event && + ff_d3d12_filter_wait_fence(cmd->fence, allocator->fence_value, cmd->fence_event, INFINITE); + + if (confirmed_done) { + ID3D12CommandAllocator_Release(allocator->command_allocator); + d3d12_cmd_free_input_refs(allocator); + } else { + av_log(ctx, AV_LOG_ERROR, + "Failed to queue a command allocator for later reuse\n"); + } + } + + return ret; +} + +int ff_d3d12_cmd_uninit(AVFilterContext *ctx, D3D12CommandContext *cmd) +{ + int gpu_confirmed_done = 1; + + cmd->ready = 0; + + if (cmd->gpu_work_submitted && cmd->queue && cmd->fence && cmd->fence_event) { + UINT64 drain_value = cmd->fence_value + 1; + + gpu_confirmed_done = 0; + + if (SUCCEEDED(ID3D12CommandQueue_Signal(cmd->queue, cmd->fence, drain_value))) { + UINT64 completed; + /* this gives one more value as defense in depth, while cost is almost nothing */ + cmd->fence_value = drain_value + 1; + completed = ID3D12Fence_GetCompletedValue(cmd->fence); + if (completed >= drain_value) + gpu_confirmed_done = 1; + else + gpu_confirmed_done = ff_d3d12_filter_wait_fence(cmd->fence, drain_value, + cmd->fence_event, INFINITE); + } + + if (!gpu_confirmed_done) { + HRESULT removed_reason = ID3D12Device_GetDeviceRemovedReason(cmd->device); + gpu_confirmed_done = FAILED(removed_reason); + if (gpu_confirmed_done) + av_log(ctx, AV_LOG_ERROR, + "D3D12 device removed (HRESULT 0x%lX) while tearing " + "down %s\n", removed_reason, ctx->filter->name); + } + } + + if (!gpu_confirmed_done) { + av_log(ctx, AV_LOG_ERROR, + "Could not confirm the GPU finished all %s work " + "during teardown\n", ctx->filter->name); + + return AVERROR_EXTERNAL; + } + + if (cmd->fence_event) { + CloseHandle(cmd->fence_event); + cmd->fence_event = NULL; + } + + if (cmd->fence) { + ID3D12Fence_Release(cmd->fence); + cmd->fence = NULL; + } + + if (d3d12_cmd_get_list(cmd)) { + ID3D12CommandList_Release(d3d12_cmd_get_list(cmd)); + memset(&cmd->list, 0, sizeof(cmd->list)); + } + + if (cmd->allocator_queue) { + D3D12CommandAllocator allocator; + while (av_fifo_read(cmd->allocator_queue, &allocator, 1) >= 0) { + ID3D12CommandAllocator_Release(allocator.command_allocator); + d3d12_cmd_free_input_refs(&allocator); + } + av_fifo_freep2(&cmd->allocator_queue); + } + + if (cmd->queue) { + ID3D12CommandQueue_Release(cmd->queue); + cmd->queue = NULL; + } + + cmd->gpu_work_submitted = 0; + + return 0; +} + +int ff_d3d12_cmd_init(AVFilterContext *ctx, D3D12CommandContext *cmd, + ID3D12Device *device, D3D12_COMMAND_LIST_TYPE type) +{ + D3D12CommandAllocator allocator = { 0 }; + const IID *list_iid; + HRESULT hr; + int ret; + + switch (type) { + case D3D12_COMMAND_LIST_TYPE_DIRECT: + list_iid = &IID_ID3D12GraphicsCommandList; + break; + case D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS: + list_iid = &IID_ID3D12VideoProcessCommandList; + break; + case D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE: + list_iid = &IID_ID3D12VideoEncodeCommandList; + break; + default: + av_log(ctx, AV_LOG_ERROR, "Unsupported command list type %d\n", type); + return AVERROR(EINVAL); + } + + if (ff_d3d12_cmd_uninit(ctx, cmd) < 0) { + av_log(ctx, AV_LOG_ERROR, + "Cannot configure a new D3D12 command queue\n"); + return AVERROR_EXTERNAL; + } + + cmd->device = device; + cmd->type = type; + + D3D12_COMMAND_QUEUE_DESC queue_desc = { + .Type = type, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 + }; + + hr = ID3D12Device_CreateCommandQueue(cmd->device, &queue_desc, &IID_ID3D12CommandQueue, (void **)&cmd->queue); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create command queue: HRESULT 0x%lX\n", hr); + ff_d3d12_cmd_uninit(ctx, cmd); + + return AVERROR_EXTERNAL; + } + + hr = ID3D12Device_CreateFence(cmd->device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, (void **)&cmd->fence); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create fence: HRESULT 0x%lX\n", hr); + ff_d3d12_cmd_uninit(ctx, cmd); + return AVERROR_EXTERNAL; + } + + cmd->fence_value = 1; + cmd->fence_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!cmd->fence_event) { + av_log(ctx, AV_LOG_ERROR, "Failed to create fence event\n"); + ff_d3d12_cmd_uninit(ctx, cmd); + + return AVERROR_EXTERNAL; + } + + cmd->allocator_queue = av_fifo_alloc2(2, sizeof(D3D12CommandAllocator), AV_FIFO_FLAG_AUTO_GROW); + if (!cmd->allocator_queue) { + ff_d3d12_cmd_uninit(ctx, cmd); + return AVERROR(ENOMEM); + } + + ret = d3d12_cmd_get_valid_command_allocator(cmd, ctx, &allocator.command_allocator); + if (ret < 0) { + ff_d3d12_cmd_uninit(ctx, cmd); + return ret; + } + + hr = ID3D12Device_CreateCommandList( + cmd->device, + 0, + cmd->type, + allocator.command_allocator, + NULL, + list_iid, + (void **)&cmd->list + ); + + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create command list: HRESULT 0x%lX\n", hr); + ID3D12CommandAllocator_Release(allocator.command_allocator); + ff_d3d12_cmd_uninit(ctx, cmd); + + return AVERROR_EXTERNAL; + } + + hr = d3d12_cmd_close_list(cmd); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to close command list: HRESULT 0x%lX\n", hr); + if (ff_d3d12_cmd_uninit(ctx, cmd) >= 0) + ID3D12CommandAllocator_Release(allocator.command_allocator); + + return AVERROR_EXTERNAL; + } + + ret = d3d12_cmd_discard_command_allocator(cmd, ctx, &allocator); + if (ret < 0) { + ff_d3d12_cmd_uninit(ctx, cmd); + return ret; + } + + cmd->ready = 1; + + return 0; +} + +/* Called after a command list error: release everything so the caller + * configures a new context. */ +static void d3d12_cmd_force_reconfigure(AVFilterContext *ctx, D3D12CommandContext *cmd, + ID3D12CommandAllocator *command_allocator) +{ + if (ff_d3d12_cmd_uninit(ctx, cmd) >= 0) + ID3D12CommandAllocator_Release(command_allocator); +} + +int ff_d3d12_cmd_begin(AVFilterContext *ctx, D3D12CommandContext *cmd) +{ + ID3D12CommandAllocator *command_allocator = NULL; + HRESULT hr; + int ret; + + ret = d3d12_cmd_get_valid_command_allocator(cmd, ctx, &command_allocator); + if (ret < 0) + return ret; + + hr = ID3D12CommandAllocator_Reset(command_allocator); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to reset command allocator: HRESULT 0x%lX\n", hr); + ID3D12CommandAllocator_Release(command_allocator); + return AVERROR_EXTERNAL; + } + + hr = d3d12_cmd_reset_list(cmd, command_allocator); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to reset command list: HRESULT 0x%lX\n", hr); + d3d12_cmd_force_reconfigure(ctx, cmd, command_allocator); + return AVERROR_EXTERNAL; + } + + cmd->command_allocator = command_allocator; + + return 0; +} + +int ff_d3d12_cmd_wait_context(AVFilterContext *ctx, D3D12CommandContext *cmd, + const D3D12CommandContext *other) +{ + UINT64 last_value; + HRESULT hr; + + if (!other->fence || other->fence_value <= 1) + return 0; + + last_value = other->fence_value - 1; + if (ID3D12Fence_GetCompletedValue(other->fence) >= last_value) + return 0; + + hr = ID3D12CommandQueue_Wait(cmd->queue, other->fence, last_value); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to wait for another command queue: HRESULT 0x%lX\n", hr); + return AVERROR_EXTERNAL; + } + cmd->gpu_work_submitted = 1; + + return 0; +} + +int ff_d3d12_cmd_sync(AVFilterContext *ctx, D3D12CommandContext *cmd, DWORD timeout_ms) +{ + if (!cmd->fence || cmd->fence_value <= 1) + return 0; + + if (!ff_d3d12_filter_wait_fence(cmd->fence, cmd->fence_value - 1, + cmd->fence_event, timeout_ms)) { + av_log(ctx, AV_LOG_ERROR, "Failed to wait for %s GPU work\n", ctx->filter->name); + return AVERROR_EXTERNAL; + } + + return 0; +} + +/* Make the queue wait for prior GPU work on frame before this submission + * accesses it. Returns 1 if a wait was enqueued, 0 if none was needed. */ +static int d3d12_cmd_wait_frame(AVFilterContext *ctx, D3D12CommandContext *cmd, + const AVFrame *frame, const char *fence_name) +{ + AVD3D12VAFrame *d3d12_frame = (AVD3D12VAFrame *)frame->data[0]; + HRESULT hr; + + if (!d3d12_frame->sync_ctx.fence || !d3d12_frame->sync_ctx.fence_value) + return 0; + + if (ID3D12Fence_GetCompletedValue(d3d12_frame->sync_ctx.fence) >= d3d12_frame->sync_ctx.fence_value) + return 0; + + hr = ID3D12CommandQueue_Wait(cmd->queue, d3d12_frame->sync_ctx.fence, + d3d12_frame->sync_ctx.fence_value); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to wait for %s: HRESULT 0x%lX\n", fence_name, hr); + return AVERROR_EXTERNAL; + } + cmd->gpu_work_submitted = 1; + + return 1; +} + +/* After a failure with work possibly queued, try to confirm the GPU is idle + * so the frames and the command allocator can be released. */ +static int d3d12_cmd_sync_after_failure(AVFilterContext *ctx, D3D12CommandContext *cmd) +{ + HRESULT removed_reason = ID3D12Device_GetDeviceRemovedReason(cmd->device); + int gpu_confirmed_done = FAILED(removed_reason); + + if (gpu_confirmed_done) { + av_log(ctx, AV_LOG_ERROR, + "D3D12 device removed (HRESULT 0x%lX); frame " + "resources are safe to release\n", removed_reason); + } else if (cmd->queue && cmd->fence && cmd->fence_event) { + UINT64 last_resort = cmd->fence_value + 1; + if (SUCCEEDED(ID3D12CommandQueue_Signal(cmd->queue, cmd->fence, last_resort))) { + UINT64 completed; + + if (last_resort >= cmd->fence_value) + cmd->fence_value = last_resort + 1; + + completed = ID3D12Fence_GetCompletedValue(cmd->fence); + if (completed >= last_resort) { + gpu_confirmed_done = 1; + } else { + gpu_confirmed_done = ff_d3d12_filter_wait_fence(cmd->fence, last_resort, + cmd->fence_event, 5000); + if (!gpu_confirmed_done) + av_log(ctx, AV_LOG_ERROR, + "Post-failure GPU wait timed out; frame " + "resources cannot be safely recycled\n"); + } + } else { + av_log(ctx, AV_LOG_ERROR, + "Best-effort post-failure GPU sync Signal also " + "failed; frame resources cannot be safely " + "recycled\n"); + } + } else { + av_log(ctx, AV_LOG_ERROR, + "No fence/queue for post-failure GPU sync; frame " + "resources cannot be safely recycled\n"); + } + + return gpu_confirmed_done; +} + +int ff_d3d12_cmd_submit(AVFilterContext *ctx, D3D12CommandContext *cmd, + AVFrame *const *inputs, int nb_inputs, AVFrame *output, + int *frames_in_use) +{ + D3D12CommandAllocator allocator = { + .command_allocator = cmd->command_allocator, + }; + AVD3D12VAFrame *output_frame = output ? (AVD3D12VAFrame *)output->data[0] : NULL; + UINT64 next_output_fence_value, next_private_fence_value; + int wait_enqueued = 0; + HRESULT hr; + int ret; + + av_assert0(nb_inputs <= D3D12_FILTER_MAX_INPUTS); + + cmd->command_allocator = NULL; + *frames_in_use = 0; + + hr = d3d12_cmd_close_list(cmd); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to close command list: HRESULT 0x%lX\n", hr); + d3d12_cmd_force_reconfigure(ctx, cmd, allocator.command_allocator); + return AVERROR_EXTERNAL; + } + + /* Wait for the input frames' fences so prior GPU work on these + * surfaces completes before the command list reads them. */ + for (int i = 0; i < nb_inputs; i++) { + ret = d3d12_cmd_wait_frame(ctx, cmd, inputs[i], "input fence"); + if (ret < 0) + goto wait_fail; + wait_enqueued |= ret; + } + + if (output) { + ret = d3d12_cmd_wait_frame(ctx, cmd, output, "output frame fence"); + if (ret < 0) + goto wait_fail; + } + + ID3D12CommandList *cmd_lists[] = { d3d12_cmd_get_list(cmd) }; + ID3D12CommandQueue_ExecuteCommandLists(cmd->queue, 1, cmd_lists); + cmd->gpu_work_submitted = 1; + + next_private_fence_value = cmd->fence_value; + + if (output_frame) { + next_output_fence_value = output_frame->sync_ctx.fence_value + 1; + + hr = ID3D12CommandQueue_Signal(cmd->queue, output_frame->sync_ctx.fence, + next_output_fence_value); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to signal output frame fence: HRESULT 0x%lX\n", hr); + ret = AVERROR_EXTERNAL; + goto submit_fail; + } + output_frame->sync_ctx.fence_value = next_output_fence_value; + } + + hr = ID3D12CommandQueue_Signal(cmd->queue, cmd->fence, next_private_fence_value); + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to signal fence: HRESULT 0x%lX\n", hr); + ret = AVERROR_EXTERNAL; + goto submit_fail; + } + + cmd->fence_value++; + + allocator.fence_value = next_private_fence_value; + for (int i = 0; i < nb_inputs; i++) { + allocator.input_refs[i] = av_frame_clone(inputs[i]); + if (!allocator.input_refs[i]) { + ret = AVERROR(ENOMEM); + goto submit_fail; + } + allocator.nb_input_refs++; + } + + return d3d12_cmd_discard_command_allocator(cmd, ctx, &allocator); + +wait_fail: + if (!wait_enqueued) + goto fail; + +submit_fail: + if (!d3d12_cmd_sync_after_failure(ctx, cmd)) { + av_log(ctx, AV_LOG_ERROR, + "Abandoning this frame's input/output surfaces and " + "command allocator without releasing them\n"); + *frames_in_use = 1; + return ret; + } + +fail: + ID3D12CommandAllocator_Release(allocator.command_allocator); + d3d12_cmd_free_input_refs(&allocator); + return ret; +} diff --git a/libavfilter/d3d12_filter.h b/libavfilter/d3d12_filter.h new file mode 100644 index 00000000000..ec746af6272 --- /dev/null +++ b/libavfilter/d3d12_filter.h @@ -0,0 +1,195 @@ +/* + * D3D12VA filter helpers + * + * 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 + */ + +#ifndef AVFILTER_D3D12_FILTER_H +#define AVFILTER_D3D12_FILTER_H + +#include "libavutil/buffer.h" +#include "libavutil/fifo.h" +#include "libavutil/frame.h" +#include "libavutil/hwcontext_d3d12va.h" +#include "libavutil/pixfmt.h" + +#include "avfilter.h" + +/** + * Maximum number of input frames (including reference frames) that one + * submission can read. + */ +#define D3D12_FILTER_MAX_INPUTS 8 + +typedef struct D3D12FilterFormat { + enum AVPixelFormat pix_fmt; + DXGI_FORMAT dxgi_format; + int is_rgb; +} D3D12FilterFormat; + +const D3D12FilterFormat *ff_d3d12_filter_format_from_pix_fmt(enum AVPixelFormat pix_fmt); + +const D3D12FilterFormat *ff_d3d12_filter_format_from_dxgi(DXGI_FORMAT dxgi_format); + +/** + * Iterate over all supported formats. + * + * @param opaque a pointer where state is stored, must point to NULL on the + * first call + * @return the next format, or NULL when the iteration is finished + */ +const D3D12FilterFormat *ff_d3d12_filter_format_iterate(void **opaque); + +/** + * Map AVFrame color properties to a DXGI color space. + * + * For an input (is_output = 0), properties that no DXGI color space can + * represent are rejected. For an output request (is_output = 1), they are + * retagged to the nearest representable value where possible. spc, range, + * primaries and trc are updated to describe the selected color space. + * + * @param input_601_hint primaries of a BT.601 input, used to keep the + * output's PAL/NTSC primaries consistent with it + */ +int ff_d3d12_filter_get_colorspace(AVFilterContext *ctx, int is_rgb, int is_output, + enum AVColorSpace *spc, + enum AVColorRange *range, + enum AVColorPrimaries *primaries, + enum AVColorTransferCharacteristic *trc, + enum AVChromaLocation chroma_loc, + enum AVColorPrimaries input_601_hint, + DXGI_COLOR_SPACE_TYPE *out_space); + +enum AVChromaLocation ff_d3d12_filter_chroma_location(DXGI_COLOR_SPACE_TYPE space); + +AVRational ff_d3d12_filter_get_input_framerate(AVFilterContext *ctx, AVFilterLink *inlink, + const AVFrame *in); + +/** + * Wait until fence reaches target, or until timeout_ms expires. + * + * @return 1 if the fence reached target, 0 otherwise + */ +int ff_d3d12_filter_wait_fence(ID3D12Fence *fence, UINT64 target, + HANDLE fence_event, DWORD timeout_ms); + +/** + * Append one transition barrier per plane of a D3D12 frame. + */ +void ff_d3d12_filter_add_frame_barriers(D3D12_RESOURCE_BARRIER *barriers, int *nb_barriers, + const AVD3D12VAFrame *frame, int nb_planes, + D3D12_RESOURCE_STATES state_before, + D3D12_RESOURCE_STATES state_after); + +/** + * Allocate and initialize a pool of D3D12 frames that can be used as + * render targets. ctx->extra_hw_frames is added to initial_pool_size. + */ +int ff_d3d12_filter_init_output_frames(AVFilterContext *ctx, AVBufferRef *device_ref, + enum AVPixelFormat sw_format, int width, int height, + int initial_pool_size, AVBufferRef **frames_ref); + +/** + * One command queue with its command list, a pool of command allocators + * and a private fence. Each submission signals the next private fence + * value; its command allocator and input references are recycled once the + * fence has passed that value. + */ +typedef struct D3D12CommandContext { + ID3D12Device *device; + D3D12_COMMAND_LIST_TYPE type; + ID3D12CommandQueue *queue; + + /* The member matching type is valid. */ + union { + ID3D12GraphicsCommandList *graphics; + ID3D12VideoProcessCommandList *video_process; + ID3D12VideoEncodeCommandList *video_encode; + } list; + + /* Owned between ff_d3d12_cmd_begin() and ff_d3d12_cmd_submit(). */ + ID3D12CommandAllocator *command_allocator; + AVFifo *allocator_queue; + + ID3D12Fence *fence; + /* Value signaled by the next submission. */ + UINT64 fence_value; + HANDLE fence_event; + + /* Set by ff_d3d12_cmd_init(), cleared when the context is torn down, + * including when a command list error tears it down. */ + int ready; + int gpu_work_submitted; +} D3D12CommandContext; + +/** + * Create the command queue, the fence and a closed command list of the + * given type, after releasing any previous configuration. type must be + * DIRECT, VIDEO_PROCESS or VIDEO_ENCODE. Releases everything on failure. + */ +int ff_d3d12_cmd_init(AVFilterContext *ctx, D3D12CommandContext *cmd, + ID3D12Device *device, D3D12_COMMAND_LIST_TYPE type); + +/** + * Get a command allocator the GPU is done with, and reset it together with + * the command list for recording. If the command list cannot be reset, the + * context is torn down and cmd->ready is cleared. + */ +int ff_d3d12_cmd_begin(AVFilterContext *ctx, D3D12CommandContext *cmd); + +/** + * Make the queue of cmd wait, on the GPU, until the latest submission of + * other has completed. Must be called before the submission of cmd that + * depends on it. Does nothing if other has no pending work. + */ +int ff_d3d12_cmd_wait_context(AVFilterContext *ctx, D3D12CommandContext *cmd, + const D3D12CommandContext *other); + +/** + * Close and execute the command list. The queue first waits for the fences + * of all inputs and of the output, then signals the output frame's fence. + * output may be NULL. References to the inputs are kept until the GPU has + * finished with them. If the command list cannot be closed, the context is + * torn down and cmd->ready is cleared. + * + * @param[out] frames_in_use set to 1 on failure if the GPU may still access + * the frames, in which case the caller must not + * free them + */ +int ff_d3d12_cmd_submit(AVFilterContext *ctx, D3D12CommandContext *cmd, + AVFrame *const *inputs, int nb_inputs, AVFrame *output, + int *frames_in_use); + +/** + * Wait on the CPU until the latest submission has completed. + * + * @return 0 on success, AVERROR_EXTERNAL on timeout or failure + */ +int ff_d3d12_cmd_sync(AVFilterContext *ctx, D3D12CommandContext *cmd, DWORD timeout_ms); + +/** + * Wait for the GPU to finish all submitted work, then release all D3D12 + * objects and clear cmd->ready. + * + * @return 0 on success, AVERROR_EXTERNAL if the GPU could not be confirmed + * idle, in which case nothing is released + */ +int ff_d3d12_cmd_uninit(AVFilterContext *ctx, D3D12CommandContext *cmd); + +#endif /* AVFILTER_D3D12_FILTER_H */ diff --git a/libavfilter/d3d12_vpp.c b/libavfilter/d3d12_vpp.c new file mode 100644 index 00000000000..3c2fa00941b --- /dev/null +++ b/libavfilter/d3d12_vpp.c @@ -0,0 +1,182 @@ +/* + * D3D12VA video processor helpers + * + * 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/log.h" +#include "libavutil/pixdesc.h" + +#include "d3d12_vpp.h" + +static void d3d12_vpp_release(D3D12VPPContext *vpp) +{ + if (vpp->video_processor) { + ID3D12VideoProcessor_Release(vpp->video_processor); + vpp->video_processor = NULL; + } + + if (vpp->video_device) { + ID3D12VideoDevice_Release(vpp->video_device); + vpp->video_device = NULL; + } +} + +/* The command context tears itself down after a command list error; the + * video processor then has to be configured again. */ +static void d3d12_vpp_check_cmd(D3D12VPPContext *vpp) +{ + if (vpp->cmd.ready) + return; + + vpp->configured = 0; + if (!vpp->cmd.queue) + d3d12_vpp_release(vpp); +} + +int ff_d3d12_vpp_uninit(AVFilterContext *ctx, D3D12VPPContext *vpp) +{ + int ret; + + vpp->configured = 0; + + ret = ff_d3d12_cmd_uninit(ctx, &vpp->cmd); + if (ret < 0) + return ret; + + d3d12_vpp_release(vpp); + + return 0; +} + +int ff_d3d12_vpp_init(AVFilterContext *ctx, D3D12VPPContext *vpp, ID3D12Device *device) +{ + HRESULT hr; + int ret; + + if (ff_d3d12_vpp_uninit(ctx, vpp) < 0) { + av_log(ctx, AV_LOG_ERROR, + "Cannot configure a new D3D12 video processor\n"); + return AVERROR_EXTERNAL; + } + + hr = ID3D12Device_QueryInterface(device, &IID_ID3D12VideoDevice, (void **)&vpp->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; + } + + ret = ff_d3d12_cmd_init(ctx, &vpp->cmd, device, D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS); + if (ret < 0) { + ff_d3d12_vpp_uninit(ctx, vpp); + return ret; + } + + return 0; +} + +void ff_d3d12_vpp_report_output_support(AVFilterContext *ctx, D3D12VPPContext *vpp, + const D3D12_FEATURE_DATA_VIDEO_PROCESS_SUPPORT *support) +{ + D3D12_FEATURE_DATA_VIDEO_PROCESS_SUPPORT probe; + const D3D12FilterFormat *map; + void *iter = NULL; + + if (av_log_get_level() < AV_LOG_DEBUG) + return; + + while ((map = ff_d3d12_filter_format_iterate(&iter))) { + DXGI_COLOR_SPACE_TYPE candidate_space; + enum AVColorRange candidate_range = map->is_rgb ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; + enum AVColorSpace candidate_spc = AVCOL_SPC_UNSPECIFIED; + enum AVColorPrimaries candidate_primaries = AVCOL_PRI_UNSPECIFIED; + enum AVColorTransferCharacteristic candidate_trc = AVCOL_TRC_UNSPECIFIED; + HRESULT hr; + + if (ff_d3d12_filter_get_colorspace(ctx, map->is_rgb, 1, &candidate_spc, + &candidate_range, &candidate_primaries, + &candidate_trc, AVCHROMA_LOC_UNSPECIFIED, + AVCOL_PRI_UNSPECIFIED, + &candidate_space) < 0) + continue; + + probe = *support; + probe.OutputFormat.Format = map->dxgi_format; + probe.OutputFormat.ColorSpace = candidate_space; + + hr = ID3D12VideoDevice_CheckFeatureSupport(vpp->video_device, D3D12_FEATURE_VIDEO_PROCESS_SUPPORT, + &probe, sizeof(probe)); + + av_log(ctx, AV_LOG_DEBUG, "Probe output %s (DXGI %d, colorspace %d): %s\n", + av_get_pix_fmt_name(map->pix_fmt), map->dxgi_format, candidate_space, + (SUCCEEDED(hr) && (probe.SupportFlags & D3D12_VIDEO_PROCESS_SUPPORT_FLAG_SUPPORTED)) ? + "supported" : "unsupported"); + } +} + +int ff_d3d12_vpp_create_processor(AVFilterContext *ctx, D3D12VPPContext *vpp, + const D3D12_VIDEO_PROCESS_OUTPUT_STREAM_DESC *output_desc, + const D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC *input_desc) +{ + HRESULT hr; + + hr = ID3D12VideoDevice_CreateVideoProcessor( + vpp->video_device, + 0, + output_desc, + 1, + input_desc, + &IID_ID3D12VideoProcessor, + (void **)&vpp->video_processor + ); + + if (FAILED(hr)) { + av_log(ctx, AV_LOG_ERROR, "Failed to create video processor: HRESULT 0x%lX\n", hr); + ff_d3d12_vpp_uninit(ctx, vpp); + return AVERROR_EXTERNAL; + } + + vpp->configured = 1; + + return 0; +} + +int ff_d3d12_vpp_begin(AVFilterContext *ctx, D3D12VPPContext *vpp) +{ + int ret = ff_d3d12_cmd_begin(ctx, &vpp->cmd); + + d3d12_vpp_check_cmd(vpp); + + return ret; +} + +int ff_d3d12_vpp_submit(AVFilterContext *ctx, D3D12VPPContext *vpp, + AVFrame *const *inputs, int nb_inputs, AVFrame *output, + int *frames_in_use) +{ + int ret = ff_d3d12_cmd_submit(ctx, &vpp->cmd, inputs, nb_inputs, output, + frames_in_use); + + d3d12_vpp_check_cmd(vpp); + + return ret; +} diff --git a/libavfilter/d3d12_vpp.h b/libavfilter/d3d12_vpp.h new file mode 100644 index 00000000000..dce0b3a0ee9 --- /dev/null +++ b/libavfilter/d3d12_vpp.h @@ -0,0 +1,89 @@ +/* + * D3D12VA video processor helpers + * + * 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 + */ + +#ifndef AVFILTER_D3D12_VPP_H +#define AVFILTER_D3D12_VPP_H + +#include "libavutil/frame.h" +#include "libavutil/hwcontext_d3d12va.h" + +#include "avfilter.h" +#include "d3d12_filter.h" + +/** + * Video processor state shared by the D3D12 filters. All work is submitted + * to the video process queue of cmd. + */ +typedef struct D3D12VPPContext { + D3D12CommandContext cmd; + + ID3D12VideoDevice *video_device; + ID3D12VideoProcessor *video_processor; + + int configured; +} D3D12VPPContext; + +/** + * Create the video device and the video process command context on device, + * after releasing any previous configuration. + */ +int ff_d3d12_vpp_init(AVFilterContext *ctx, D3D12VPPContext *vpp, ID3D12Device *device); + +/** + * Log, at debug level, which output formats the video processor supports + * for the input described in support. + */ +void ff_d3d12_vpp_report_output_support(AVFilterContext *ctx, D3D12VPPContext *vpp, + const D3D12_FEATURE_DATA_VIDEO_PROCESS_SUPPORT *support); + +/** + * Create the video processor and mark vpp as configured. Must be called + * after ff_d3d12_vpp_init(). Releases everything on failure. + */ +int ff_d3d12_vpp_create_processor(AVFilterContext *ctx, D3D12VPPContext *vpp, + const D3D12_VIDEO_PROCESS_OUTPUT_STREAM_DESC *output_desc, + const D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC *input_desc); + +/** + * Start recording into vpp->cmd.list.video_process, see ff_d3d12_cmd_begin(). + * Clears vpp->configured if the command context was torn down. + */ +int ff_d3d12_vpp_begin(AVFilterContext *ctx, D3D12VPPContext *vpp); + +/** + * Execute the recorded commands, see ff_d3d12_cmd_submit(). Clears + * vpp->configured if the command context was torn down. + */ +int ff_d3d12_vpp_submit(AVFilterContext *ctx, D3D12VPPContext *vpp, + AVFrame *const *inputs, int nb_inputs, AVFrame *output, + int *frames_in_use); + +/** + * Wait for the GPU to finish all submitted work, then release all D3D12 + * objects and clear vpp->configured. + * + * @return 0 on success, AVERROR_EXTERNAL if the GPU could not be confirmed + * idle, in which case nothing is released + */ +int ff_d3d12_vpp_uninit(AVFilterContext *ctx, D3D12VPPContext *vpp); + +#endif /* AVFILTER_D3D12_VPP_H */ -- 2.54.0 >From c3fd08092fc9c2ce96bffbd1b443d52e94e670c6 Mon Sep 17 00:00:00 2001 From: Steven Xiao <[email protected]> Date: Fri, 6 Nov 2026 18:34:44 -0500 Subject: [PATCH 2/2] avfilter/vf_scale_d3d12: use the shared D3D12 helpers Replace the filter's own command queue, command list, command allocator pool, fence handling, GPU teardown, format table, color space mapping and output frame pool setup with the helpers from d3d12_filter.c and d3d12_vpp.c. No functional change. Signed-off-by: younengxiao <[email protected]> --- libavfilter/Makefile | 2 +- libavfilter/vf_scale_d3d12.c | 1100 ++-------------------------------- 2 files changed, 54 insertions(+), 1048 deletions(-) diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 1b5c11fb4c2..cf9b25ee3ae 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -477,7 +477,7 @@ OBJS-$(CONFIG_ROTATE_FILTER) += vf_rotate.o OBJS-$(CONFIG_SAB_FILTER) += vf_sab.o OBJS-$(CONFIG_SCALE_FILTER) += vf_scale.o scale_eval.o framesync.o OBJS-$(CONFIG_SCALE_D3D11_FILTER) += vf_scale_d3d11.o scale_eval.o -OBJS-$(CONFIG_SCALE_D3D12_FILTER) += vf_scale_d3d12.o scale_eval.o +OBJS-$(CONFIG_SCALE_D3D12_FILTER) += vf_scale_d3d12.o scale_eval.o d3d12_filter.o d3d12_vpp.o OBJS-$(CONFIG_SCALE_CUDA_FILTER) += vf_scale_cuda.o scale_eval.o scale_filters.o \ vf_scale_cuda.ptx.o cuda/load_helper.o OBJS-$(CONFIG_SCALE_QSV_FILTER) += vf_vpp_qsv.o diff --git a/libavfilter/vf_scale_d3d12.c b/libavfilter/vf_scale_d3d12.c index cadd48e5a02..d27a5ebaaa9 100644 --- a/libavfilter/vf_scale_d3d12.c +++ b/libavfilter/vf_scale_d3d12.c @@ -18,7 +18,6 @@ #define COBJMACROS -#include "libavutil/fifo.h" #include "libavutil/frame.h" #include "libavutil/opt.h" #include "libavutil/pixdesc.h" @@ -28,51 +27,12 @@ #include "libavutil/hwcontext_d3d12va.h" #include "libavutil/hwcontext_d3d12va_internal.h" +#include "d3d12_vpp.h" #include "filters.h" #include "formats.h" #include "scale_eval.h" #include "video.h" -typedef struct D3D12ScaleCommandAllocator { - ID3D12CommandAllocator *command_allocator; - UINT64 fence_value; - AVFrame *input_ref; -} D3D12ScaleCommandAllocator; - -typedef struct D3D12ScaleFormatMap { - enum AVPixelFormat pix_fmt; - DXGI_FORMAT dxgi_format; - int is_rgb; -} D3D12ScaleFormatMap; - -static const D3D12ScaleFormatMap d3d12_scale_format_map[] = { - { AV_PIX_FMT_NV12, DXGI_FORMAT_NV12, 0 }, - { AV_PIX_FMT_P010, DXGI_FORMAT_P010, 0 }, - { AV_PIX_FMT_P012, DXGI_FORMAT_P016, 0 }, - { AV_PIX_FMT_BGRA, DXGI_FORMAT_B8G8R8A8_UNORM, 1 }, - { AV_PIX_FMT_RGBA, DXGI_FORMAT_R8G8B8A8_UNORM, 1 }, - { AV_PIX_FMT_X2BGR10, DXGI_FORMAT_R10G10B10A2_UNORM, 1 }, - { AV_PIX_FMT_RGBAF16, DXGI_FORMAT_R16G16B16A16_FLOAT, 1 }, -}; - -static const D3D12ScaleFormatMap *d3d12_scale_map_from_pixfmt(enum AVPixelFormat fmt) -{ - for (int i = 0; i < FF_ARRAY_ELEMS(d3d12_scale_format_map); i++) - if (d3d12_scale_format_map[i].pix_fmt == fmt) - return &d3d12_scale_format_map[i]; - - return NULL; -} - -static const D3D12ScaleFormatMap *d3d12_scale_map_from_dxgi(DXGI_FORMAT fmt) -{ - for (int i = 0; i < FF_ARRAY_ELEMS(d3d12_scale_format_map); i++) - if (d3d12_scale_format_map[i].dxgi_format == fmt) - return &d3d12_scale_format_map[i]; - - return NULL; -} - typedef struct ScaleD3D12Context { const AVClass *classCtx; char *w_expr; @@ -87,22 +47,8 @@ typedef struct ScaleD3D12Context { enum AVColorTransferCharacteristic color_transfer; enum AVColorPrimaries color_primaries; - /* D3D12 objects */ ID3D12Device *device; - ID3D12VideoDevice *video_device; - ID3D12VideoProcessor *video_processor; - ID3D12CommandQueue *command_queue; - ID3D12VideoProcessCommandList *command_list; - - AVFifo *allocator_queue; - - int configured; - int gpu_work_submitted; - - /* Synchronization */ - ID3D12Fence *fence; - UINT64 fence_value; - HANDLE fence_event; + D3D12VPPContext vpp; /* Buffer references */ AVBufferRef *hw_device_ctx; @@ -163,212 +109,6 @@ static av_cold int scale_d3d12_init(AVFilterContext *ctx) { return 0; } -static int scale_d3d12_wait_fence_exact(ID3D12Fence *fence, UINT64 target, - HANDLE fence_event, DWORD timeout_ms) -{ - int has_deadline = (timeout_ms != INFINITE); - ULONGLONG deadline = 0; - - if (ID3D12Fence_GetCompletedValue(fence) >= target) - return 1; - - if (FAILED(ID3D12Fence_SetEventOnCompletion(fence, target, fence_event))) - return 0; - - if (has_deadline) - deadline = GetTickCount64() + timeout_ms; - - for (;;) { - DWORD wait_result = WaitForSingleObject(fence_event, timeout_ms); - - if (wait_result == WAIT_OBJECT_0) { - if (ID3D12Fence_GetCompletedValue(fence) >= target) - return 1; - if (has_deadline) { - ULONGLONG now = GetTickCount64(); - if (now >= deadline) - return 0; - timeout_ms = (DWORD)(deadline - now); - } - continue; - } - - return 0; - } -} - -static int scale_d3d12_get_valid_command_allocator(ScaleD3D12Context *s, AVFilterContext *ctx, - ID3D12CommandAllocator **ppAllocator) -{ - HRESULT hr; - D3D12ScaleCommandAllocator allocator; - - if (av_fifo_peek(s->allocator_queue, &allocator, 1, 0) >= 0) { - UINT64 completed = ID3D12Fence_GetCompletedValue(s->fence); - if (completed >= allocator.fence_value) { - *ppAllocator = allocator.command_allocator; - av_fifo_read(s->allocator_queue, &allocator, 1); - av_frame_free(&allocator.input_ref); - return 0; - } - } - - hr = ID3D12Device_CreateCommandAllocator(s->device, D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS, - &IID_ID3D12CommandAllocator, (void **)ppAllocator); - if (FAILED(hr)) { - av_log(ctx, AV_LOG_ERROR, "Failed to create a new command allocator: HRESULT 0x%lX\n", hr); - return AVERROR_EXTERNAL; - } - - return 0; -} - -static int scale_d3d12_discard_command_allocator(ScaleD3D12Context *s, AVFilterContext *ctx, - ID3D12CommandAllocator *pAllocator, - UINT64 fence_value, AVFrame *input_ref) -{ - D3D12ScaleCommandAllocator allocator = { - .command_allocator = pAllocator, - .fence_value = fence_value, - .input_ref = input_ref, - }; - int ret = av_fifo_write(s->allocator_queue, &allocator, 1); - - if (ret < 0) { - int confirmed_done = s->fence && s->fence_event && - scale_d3d12_wait_fence_exact(s->fence, fence_value, s->fence_event, INFINITE); - - if (confirmed_done) { - ID3D12CommandAllocator_Release(pAllocator); - av_frame_free(&input_ref); - } else { - av_log(ctx, AV_LOG_ERROR, - "Failed to queue a command allocator for later reuse\n"); - } - } - - return ret; -} - -static int release_d3d12_resources(ScaleD3D12Context *s, AVFilterContext *ctx) { - int gpu_confirmed_done = 1; - - s->configured = 0; - - if (s->gpu_work_submitted && s->command_queue && s->fence && s->fence_event) { - UINT64 drain_value = s->fence_value + 1; - - gpu_confirmed_done = 0; - - if (SUCCEEDED(ID3D12CommandQueue_Signal(s->command_queue, s->fence, drain_value))) { - UINT64 completed; - /* this gives one more value as defense in depth, while cost is almost nothing */ - s->fence_value = drain_value + 1; - completed = ID3D12Fence_GetCompletedValue(s->fence); - if (completed >= drain_value) - gpu_confirmed_done = 1; - else - gpu_confirmed_done = scale_d3d12_wait_fence_exact(s->fence, drain_value, - s->fence_event, INFINITE); - } - - if (!gpu_confirmed_done) { - HRESULT removed_reason = ID3D12Device_GetDeviceRemovedReason(s->device); - gpu_confirmed_done = FAILED(removed_reason); - if (gpu_confirmed_done) - av_log(ctx, AV_LOG_ERROR, - "D3D12 device removed (HRESULT 0x%lX) while tearing " - "down scale_d3d12\n", removed_reason); - } - } - - if (!gpu_confirmed_done) { - av_log(ctx, AV_LOG_ERROR, - "Could not confirm the GPU finished all scale_d3d12 work " - "during teardown\n"); - - return 0; - } - - 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->allocator_queue) { - D3D12ScaleCommandAllocator allocator; - while (av_fifo_read(s->allocator_queue, &allocator, 1) >= 0) { - ID3D12CommandAllocator_Release(allocator.command_allocator); - av_frame_free(&allocator.input_ref); - } - av_fifo_freep2(&s->allocator_queue); - } - - 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; - } - - s->gpu_work_submitted = 0; - - return 1; -} - -static int d3d12_scale_known_matrix(enum AVColorSpace spc) -{ - switch (spc) { - case AVCOL_SPC_UNSPECIFIED: - case AVCOL_SPC_BT709: - case AVCOL_SPC_BT470BG: - case AVCOL_SPC_SMPTE170M: - case AVCOL_SPC_BT2020_NCL: - case AVCOL_SPC_BT2020_CL: - return 1; - - default: - return 0; - } -} - -static int d3d12_scale_known_sdr_trc(enum AVColorTransferCharacteristic trc) -{ - switch (trc) { - case AVCOL_TRC_UNSPECIFIED: - case AVCOL_TRC_BT709: - case AVCOL_TRC_GAMMA22: - case AVCOL_TRC_GAMMA28: - case AVCOL_TRC_SMPTE170M: - case AVCOL_TRC_SMPTE240M: - case AVCOL_TRC_IEC61966_2_1: - case AVCOL_TRC_BT2020_10: - case AVCOL_TRC_BT2020_12: - return 1; - - default: - return 0; - } -} - static int d3d12_scale_trc_group(enum AVColorTransferCharacteristic trc) { switch (trc) { @@ -452,483 +192,21 @@ static int d3d12_scale_finalize_output_trc(AVFilterContext *ctx, return AVERROR(EINVAL); } -static int d3d12_scale_reconcile_primaries(AVFilterContext *ctx, int is_output, - enum AVColorPrimaries *primaries, - enum AVColorPrimaries expected_primaries, - const char *space_description) -{ - if (*primaries != AVCOL_PRI_UNSPECIFIED && *primaries != expected_primaries) { - const char *old_name = av_color_primaries_name(*primaries); - const char *new_name = av_color_primaries_name(expected_primaries); - if (is_output) { - av_log(ctx, AV_LOG_WARNING, - "%s forces fixed primaries; retagging output from " - "%s to %s\n", - space_description, - old_name ? old_name : "unknown", new_name ? new_name : "unknown"); - } else { - av_log(ctx, AV_LOG_ERROR, - "%s forces fixed primaries, which disagrees with " - "this input's %s primaries tag\n", - space_description, old_name ? old_name : "unknown"); - - return AVERROR(EINVAL); - } - } - - *primaries = expected_primaries; - return 0; -} - -static int d3d12_scale_reject_unrepresentable_nonleft(AVFilterContext *ctx, int is_output, - enum AVChromaLocation chroma_loc) -{ - if (chroma_loc == AVCHROMA_LOC_UNSPECIFIED || chroma_loc == AVCHROMA_LOC_LEFT) - return 0; - - if (is_output) { - av_log(ctx, AV_LOG_WARNING, - "This DXGI color space has no chroma entry other than " - "left-sited; the output will actually be left-sited\n"); - - return 0; - } - - av_log(ctx, AV_LOG_ERROR, - "This DXGI color space has no chroma entry other than " - "left-sited, but this input is tagged with a different siting\n"); - - return AVERROR(EINVAL); -} - -static int d3d12_scale_reject_unrepresentable_left(AVFilterContext *ctx, int is_output, - enum AVChromaLocation chroma_loc) -{ - if (chroma_loc == AVCHROMA_LOC_UNSPECIFIED || chroma_loc == AVCHROMA_LOC_TOPLEFT) - return 0; - - if (is_output) { - av_log(ctx, AV_LOG_WARNING, - "HLG has no DXGI entry with any chroma siting other than " - "top-left; the output will actually be top-left-sited\n"); - - return 0; - } - - av_log(ctx, AV_LOG_ERROR, - "HLG has no DXGI entry with any chroma siting other than " - "top-left, but this input is tagged with a different siting\n"); - - return AVERROR(EINVAL); -} - -static int d3d12_scale_reject_unrepresentable_siting_2020(AVFilterContext *ctx, int is_output, - enum AVChromaLocation chroma_loc) -{ - if (chroma_loc == AVCHROMA_LOC_UNSPECIFIED || - chroma_loc == AVCHROMA_LOC_TOPLEFT || - chroma_loc == AVCHROMA_LOC_LEFT) - return 0; - - if (is_output) { - av_log(ctx, AV_LOG_WARNING, - "DXGI BT.2020 only supports top-left/left chroma " - "siting; output will use one of those instead\n"); - - return 0; - } - - av_log(ctx, AV_LOG_ERROR, - "DXGI BT.2020 only supports top-left/left chroma siting, " - "but this input is tagged with a different siting\n"); - - return AVERROR(EINVAL); -} - -static int d3d12_scale_get_colorspace(AVFilterContext *ctx, int is_rgb, int is_output, - enum AVColorSpace *spc, - enum AVColorRange *range, - enum AVColorPrimaries *primaries, - enum AVColorTransferCharacteristic *trc, - enum AVChromaLocation chroma_loc, - enum AVColorPrimaries input_601_hint, - DXGI_COLOR_SPACE_TYPE *out_space) -{ - int full_range = (*range == AVCOL_RANGE_JPEG); - int ret; - - if (!is_rgb && *spc == AVCOL_SPC_BT2020_CL) { - if (is_output) { - av_log(ctx, AV_LOG_WARNING, - "BT.2020 constant-luminance has no DXGI color space; " - "retagging output as bt2020nc\n"); - *spc = AVCOL_SPC_BT2020_NCL; - } else { - av_log(ctx, AV_LOG_ERROR, - "BT.2020 constant-luminance matrix has no DXGI color " - "space; cannot process this input without misinterpreting\n"); - - return AVERROR(EINVAL); - } - } - - if (!is_rgb && !d3d12_scale_known_matrix(*spc)) { - const char *name = av_color_space_name(*spc); - av_log(ctx, AV_LOG_ERROR, - "%s matrix has no DXGI YCbCr color space; only BT.601, " - "BT.709 or BT.2020 are supported\n", name ? name : "this"); - - return AVERROR(EINVAL); - } - - if (!is_rgb && *trc != AVCOL_TRC_SMPTE2084 && *trc != AVCOL_TRC_ARIB_STD_B67 && - !d3d12_scale_known_sdr_trc(*trc)) { - const char *name = av_color_transfer_name(*trc); - av_log(ctx, AV_LOG_ERROR, - "%s transfer has no DXGI YCbCr color space; only SDR " - "gamma, smpte2084 or HLG (arib-std-b67) are " - "supported\n", - name ? name : "this"); - - return AVERROR(EINVAL); - } - - int primaries_is_2020 = (*primaries == AVCOL_PRI_BT2020); - int is_2020 = (*spc == AVCOL_SPC_BT2020_NCL || *spc == AVCOL_SPC_BT2020_CL || - (*spc == AVCOL_SPC_UNSPECIFIED && primaries_is_2020)); - int is_601 = (*spc == AVCOL_SPC_BT470BG || *spc == AVCOL_SPC_SMPTE170M || - (*spc == AVCOL_SPC_UNSPECIFIED && - (*primaries == AVCOL_PRI_BT470BG || *primaries == AVCOL_PRI_SMPTE170M))); - - if (is_rgb) { - enum AVColorPrimaries expected_primaries; - - switch (*trc) { - case AVCOL_TRC_SMPTE2084: - *out_space = full_range ? DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 - : DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020; - expected_primaries = AVCOL_PRI_BT2020; - break; - - case AVCOL_TRC_ARIB_STD_B67: - if (is_output) - av_log(ctx, AV_LOG_ERROR, - "HLG has no RGB DXGI color space; use a YUV " - "output format or a supported out_color_transfer\n"); - else - av_log(ctx, AV_LOG_ERROR, - "HLG transfer characteristic has no RGB DXGI color " - "space; this RGB input cannot be processed\n"); - return AVERROR(EINVAL); - - case AVCOL_TRC_LINEAR: - if (!full_range) { - if (is_output) - av_log(ctx, AV_LOG_ERROR, - "Studio range linear RGB has no DXGI color " - "space; use out_range=full, or a non-linear " - "out_color_transfer (e.g. bt709)\n"); - else - av_log(ctx, AV_LOG_ERROR, - "Studio range linear RGB has no DXGI color " - "space; this input cannot be processed\n"); - - return AVERROR(EINVAL); - } - *out_space = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709; - expected_primaries = AVCOL_PRI_BT709; - break; - - default: - if (!d3d12_scale_known_sdr_trc(*trc)) { - const char *name = av_color_transfer_name(*trc); - av_log(ctx, AV_LOG_ERROR, - "%s transfer has no RGB DXGI color space; only " - "SDR gamma, linear or smpte2084 are " - "supported\n", name ? name : "this"); - - return AVERROR(EINVAL); - } - if (primaries_is_2020) { - *out_space = full_range ? DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020 - : DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020; - expected_primaries = AVCOL_PRI_BT2020; - } else { - *out_space = full_range ? DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 - : DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709; - expected_primaries = AVCOL_PRI_BT709; - } - break; - } - - return d3d12_scale_reconcile_primaries(ctx, is_output, primaries, expected_primaries, - "this RGB transfer characteristic"); - } - - /* YCbCr family. */ - if (is_2020) { - int want_topleft = (chroma_loc == AVCHROMA_LOC_TOPLEFT); - ret = d3d12_scale_reject_unrepresentable_siting_2020(ctx, is_output, chroma_loc); - if (ret < 0) - return ret; - - if (*trc == AVCOL_TRC_SMPTE2084) { - if (full_range) { - av_log(ctx, AV_LOG_ERROR, - "No full-range smpte2084 YCbCr DXGI color space " - "exists; cannot process this %s\n", - is_output ? "output request" : "input"); - - return AVERROR(EINVAL); - } - *out_space = want_topleft ? DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020 - : DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020; - } else if (*trc == AVCOL_TRC_ARIB_STD_B67) { - ret = d3d12_scale_reject_unrepresentable_left(ctx, is_output, chroma_loc); - if (ret < 0) - return ret; - *out_space = full_range ? DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020 - : DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020; - } else { - if (want_topleft && full_range) { - if (!is_output) { - av_log(ctx, AV_LOG_ERROR, - "Full-range BT.2020 SDR has no top-left-sited " - "DXGI YCbCr color space\n"); - - return AVERROR(EINVAL); - } - av_log(ctx, AV_LOG_WARNING, - "Full-range BT.2020 SDR has no top-left-sited DXGI " - "YCbCr color space; falling back to left-sited\n"); - want_topleft = 0; - } - if (want_topleft) - *out_space = DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020; - else - *out_space = full_range ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020 - : DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020; - } - - ret = d3d12_scale_reconcile_primaries(ctx, is_output, primaries, AVCOL_PRI_BT2020, - "a BT.2020 matrix"); - if (ret < 0) - return ret; - } else if (is_601) { - if (*trc == AVCOL_TRC_SMPTE2084 || *trc == AVCOL_TRC_ARIB_STD_B67) { - if (is_output) - av_log(ctx, AV_LOG_ERROR, - "%s transfer has no BT.601 DXGI color space; use " - "out_color_matrix=bt2020nc or an SDR transfer\n", - *trc == AVCOL_TRC_SMPTE2084 ? "smpte2084" : "HLG"); - else - av_log(ctx, AV_LOG_ERROR, - "%s transfer has no BT.601 DXGI color space; this " - "input cannot be processed.\n", - *trc == AVCOL_TRC_SMPTE2084 ? "smpte2084" : "HLG"); - - return AVERROR(EINVAL); - } - ret = d3d12_scale_reject_unrepresentable_nonleft(ctx, is_output, chroma_loc); - if (ret < 0) - return ret; - *out_space = full_range ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601 - : DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601; - - enum AVColorPrimaries this_601_primaries = - *spc == AVCOL_SPC_BT470BG ? AVCOL_PRI_BT470BG : - *spc == AVCOL_SPC_SMPTE170M ? AVCOL_PRI_SMPTE170M : - *primaries; - - if (is_output && input_601_hint != AVCOL_PRI_UNSPECIFIED) { - if (*primaries != AVCOL_PRI_UNSPECIFIED && *primaries != input_601_hint) { - const char *in_name = av_color_primaries_name(input_601_hint); - const char *out_name = av_color_primaries_name(*primaries); - av_log(ctx, AV_LOG_ERROR, - "DXGI's BT.601 color spaces cannot distinguish " - "PAL/NTSC primaries (%s input, %s output)\n", - in_name ? in_name : "unknown", out_name ? out_name : "unknown"); - - return AVERROR(EINVAL); - } - if (*primaries != input_601_hint) { - const char *new_name = av_color_primaries_name(input_601_hint); - av_log(ctx, AV_LOG_WARNING, - "BT.601 cannot change primaries; tagging output " - "as %s\n", - new_name ? new_name : "unknown"); - } - *primaries = input_601_hint; - } else if (!is_output) { - if (*primaries != AVCOL_PRI_UNSPECIFIED && - *primaries != AVCOL_PRI_BT470BG && *primaries != AVCOL_PRI_SMPTE170M) { - const char *old_name = av_color_primaries_name(*primaries); - av_log(ctx, AV_LOG_ERROR, - "BT.601 only represents PAL/NTSC primaries, " - "which disagrees with this input's %s tag\n", - old_name ? old_name : "unknown"); - - return AVERROR(EINVAL); - } - if (*primaries == AVCOL_PRI_UNSPECIFIED) - *primaries = this_601_primaries; - } else { - ret = d3d12_scale_reconcile_primaries(ctx, is_output, primaries, this_601_primaries, - "this BT.601 matrix"); - if (ret < 0) - return ret; - } - } else { - /* Same gap as the BT.601 case above: no smpte2084/HLG DXGI color - * space exists paired with a BT.709 matrix either. */ - if (*trc == AVCOL_TRC_SMPTE2084 || *trc == AVCOL_TRC_ARIB_STD_B67) { - if (is_output) - av_log(ctx, AV_LOG_ERROR, - "%s has no BT.709 DXGI space; use " - "out_color_matrix=bt2020nc or SDR\n", - *trc == AVCOL_TRC_SMPTE2084 ? "smpte2084" : "HLG"); - else - av_log(ctx, AV_LOG_ERROR, - "%s has no BT.709 DXGI space; retag/convert to " - "BT.2020 or SDR before scale_d3d12\n", - *trc == AVCOL_TRC_SMPTE2084 ? "smpte2084" : "HLG"); - - return AVERROR(EINVAL); - } - ret = d3d12_scale_reject_unrepresentable_nonleft(ctx, is_output, chroma_loc); - if (ret < 0) - return ret; - *out_space = full_range ? DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709 - : DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709; ///< Default to Rec.709 - - ret = d3d12_scale_reconcile_primaries(ctx, is_output, primaries, AVCOL_PRI_BT709, - "the default BT.709 matrix"); - if (ret < 0) - return ret; - } - - return 0; -} - -static enum AVChromaLocation d3d12_scale_dxgi_chroma_location(DXGI_COLOR_SPACE_TYPE space) -{ - switch (space) { - case DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020: - case DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020: - case DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020: - case DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020: - case DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_TOPLEFT_P2020: - return AVCHROMA_LOC_TOPLEFT; - - default: - return AVCHROMA_LOC_LEFT; - } -} - -static void scale_d3d12_report_output_support(ScaleD3D12Context *s, AVFilterContext *ctx) -{ - D3D12_FEATURE_DATA_VIDEO_PROCESS_SUPPORT probe; - - if (av_log_get_level() < AV_LOG_DEBUG) - return; - - for (int i = 0; i < FF_ARRAY_ELEMS(d3d12_scale_format_map); i++) { - const D3D12ScaleFormatMap *map = &d3d12_scale_format_map[i]; - DXGI_COLOR_SPACE_TYPE candidate_space; - enum AVColorRange candidate_range = map->is_rgb ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; - enum AVColorSpace candidate_spc = AVCOL_SPC_UNSPECIFIED; - enum AVColorPrimaries candidate_primaries = AVCOL_PRI_UNSPECIFIED; - enum AVColorTransferCharacteristic candidate_trc = AVCOL_TRC_UNSPECIFIED; - HRESULT hr; - - if (d3d12_scale_get_colorspace(ctx, map->is_rgb, 1, &candidate_spc, - &candidate_range, &candidate_primaries, - &candidate_trc, AVCHROMA_LOC_UNSPECIFIED, - AVCOL_PRI_UNSPECIFIED, - &candidate_space) < 0) - continue; - - probe = s->process_support; - probe.OutputFormat.Format = map->dxgi_format; - probe.OutputFormat.ColorSpace = candidate_space; - - hr = ID3D12VideoDevice_CheckFeatureSupport(s->video_device, D3D12_FEATURE_VIDEO_PROCESS_SUPPORT, - &probe, sizeof(probe)); - - av_log(ctx, AV_LOG_DEBUG, "Probe output %s (DXGI %d, colorspace %d): %s\n", - av_get_pix_fmt_name(map->pix_fmt), map->dxgi_format, candidate_space, - (SUCCEEDED(hr) && (probe.SupportFlags & D3D12_VIDEO_PROCESS_SUPPORT_FLAG_SUPPORTED)) ? - "supported" : "unsupported"); - } -} - -static AVRational get_input_framerate(AVFilterContext *ctx, AVFilterLink *inlink, AVFrame *in) -{ - AVRational framerate = {0, 0}; - FilterLink *inl = ff_filter_link(inlink); - - if (inl->frame_rate.num > 0 && inl->frame_rate.den > 0) { - framerate = inl->frame_rate; - } else 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 { - framerate.num = 30; - framerate.den = 1; - av_log(ctx, AV_LOG_WARNING, "Input framerate not determinable, defaulting to 30fps\n"); - } - - return framerate; -} - static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext *ctx) { HRESULT hr; int ret; - ID3D12CommandAllocator *command_allocator = NULL; - - if (s->video_device || s->command_queue || s->fence || s->fence_event || - s->command_list || s->video_processor || s->allocator_queue) { - if (!release_d3d12_resources(s, ctx)) { - av_log(ctx, AV_LOG_ERROR, - "Cannot configure a new D3D12 video processor\n"); - return AVERROR_EXTERNAL; - } - } if (s->output_format == DXGI_FORMAT_UNKNOWN) { av_log(ctx, AV_LOG_ERROR, "Output format not initialized\n"); return AVERROR(EINVAL); } - AVHWDeviceContext *hwctx = (AVHWDeviceContext *)s->hw_device_ctx->data; - AVD3D12VADeviceContext *d3d12_hwctx = (AVD3D12VADeviceContext *)hwctx->hwctx; - s->device = d3d12_hwctx->device; - av_log(ctx, AV_LOG_VERBOSE, "Configuring D3D12 video processor: %dx%d -> %dx%d\n", s->input_width, s->input_height, 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); - release_d3d12_resources(s, ctx); - - return AVERROR_EXTERNAL; - } + ret = ff_d3d12_vpp_init(ctx, &s->vpp, s->device); + if (ret < 0) + return ret; s->process_support.NodeIndex = 0; @@ -948,7 +226,7 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext s->process_support.OutputStereoFormat = D3D12_VIDEO_FRAME_STEREO_FORMAT_NONE; hr = ID3D12VideoDevice_CheckFeatureSupport( - s->video_device, + s->vpp.video_device, D3D12_FEATURE_VIDEO_PROCESS_SUPPORT, &s->process_support, sizeof(s->process_support) @@ -956,7 +234,7 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext if (FAILED(hr)) { av_log(ctx, AV_LOG_ERROR, "Video process feature not supported: HRESULT 0x%lX\n", hr); - release_d3d12_resources(s, ctx); + ff_d3d12_vpp_uninit(ctx, &s->vpp); return AVERROR_EXTERNAL; } @@ -965,14 +243,14 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext av_log(ctx, AV_LOG_ERROR, "D3D12 video processor does not support converting %s (colorspace %d) to %s " "(colorspace %d) on this device\n", - av_get_pix_fmt_name(d3d12_scale_map_from_dxgi(s->input_format) ? - d3d12_scale_map_from_dxgi(s->input_format)->pix_fmt : AV_PIX_FMT_NONE), + av_get_pix_fmt_name(ff_d3d12_filter_format_from_dxgi(s->input_format) ? + ff_d3d12_filter_format_from_dxgi(s->input_format)->pix_fmt : AV_PIX_FMT_NONE), s->input_colorspace, - av_get_pix_fmt_name(d3d12_scale_map_from_dxgi(s->output_format) ? - d3d12_scale_map_from_dxgi(s->output_format)->pix_fmt : AV_PIX_FMT_NONE), + av_get_pix_fmt_name(ff_d3d12_filter_format_from_dxgi(s->output_format) ? + ff_d3d12_filter_format_from_dxgi(s->output_format)->pix_fmt : AV_PIX_FMT_NONE), s->output_colorspace); - scale_d3d12_report_output_support(s, ctx); - release_d3d12_resources(s, ctx); + ff_d3d12_vpp_report_output_support(ctx, &s->vpp, &s->process_support); + ff_d3d12_vpp_uninit(ctx, &s->vpp); return AVERROR(ENOSYS); } @@ -1028,84 +306,9 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext .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); - release_d3d12_resources(s, ctx); - return AVERROR_EXTERNAL; - } - - 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); - release_d3d12_resources(s, ctx); - 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"); - release_d3d12_resources(s, ctx); - - return AVERROR_EXTERNAL; - } - - s->allocator_queue = av_fifo_alloc2(2, sizeof(D3D12ScaleCommandAllocator), AV_FIFO_FLAG_AUTO_GROW); - if (!s->allocator_queue) { - release_d3d12_resources(s, ctx); - return AVERROR(ENOMEM); - } - - ret = scale_d3d12_get_valid_command_allocator(s, ctx, &command_allocator); - if (ret < 0) { - release_d3d12_resources(s, ctx); + ret = ff_d3d12_vpp_create_processor(ctx, &s->vpp, &processor_output_desc, &processor_input_desc); + if (ret < 0) return ret; - } - - hr = ID3D12Device_CreateCommandList( - s->device, - 0, - D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS, - 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); - ID3D12CommandAllocator_Release(command_allocator); - release_d3d12_resources(s, ctx); - - return AVERROR_EXTERNAL; - } - - 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); - if (release_d3d12_resources(s, ctx)) - ID3D12CommandAllocator_Release(command_allocator); - - return AVERROR_EXTERNAL; - } - - ret = scale_d3d12_discard_command_allocator(s, ctx, command_allocator, 0, NULL); - if (ret < 0) { - release_d3d12_resources(s, ctx); - return ret; - } - - s->configured = 1; av_log(ctx, AV_LOG_VERBOSE, "D3D12 video processor successfully configured\n"); return 0; @@ -1118,10 +321,7 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) AVFilterLink *outlink = ctx->outputs[0]; AVFrame *out = NULL; int ret = 0; - HRESULT hr; - ID3D12CommandAllocator *command_allocator = NULL; - HRESULT removed_reason; - int gpu_confirmed_done; + int frames_in_use; if (!in) { av_log(ctx, AV_LOG_ERROR, "Null input frame\n"); @@ -1179,9 +379,9 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) goto fail; } - if (!s->configured) { + if (!s->vpp.configured) { AVHWFramesContext *input_frames_ctx = (AVHWFramesContext *)in->hw_frames_ctx->data; - const D3D12ScaleFormatMap *in_map, *out_map; + const D3D12FilterFormat *in_map, *out_map; enum AVColorSpace out_spc; enum AVColorRange out_range; enum AVColorPrimaries out_primaries; @@ -1194,7 +394,7 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) s->input_format = input_hwctx->format; if (s->input_format == DXGI_FORMAT_UNKNOWN) { - in_map = d3d12_scale_map_from_pixfmt(input_frames_ctx->sw_format); + in_map = ff_d3d12_filter_format_from_pix_fmt(input_frames_ctx->sw_format); if (!in_map) { av_log(ctx, AV_LOG_ERROR, "Unsupported input format: %s\n", av_get_pix_fmt_name(input_frames_ctx->sw_format)); @@ -1204,7 +404,7 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) s->input_format = in_map->dxgi_format; } - in_map = d3d12_scale_map_from_dxgi(s->input_format); + in_map = ff_d3d12_filter_format_from_dxgi(s->input_format); s->input_is_rgb = in_map ? in_map->is_rgb : 0; const AVPixFmtDescriptor *in_desc = in_map ? av_pix_fmt_desc_get(in_map->pix_fmt) : NULL; s->input_has_alpha = in_desc && (in_desc->flags & AV_PIX_FMT_FLAG_ALPHA); @@ -1215,9 +415,9 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) enum AVColorPrimaries in_primaries = in->color_primaries; enum AVColorTransferCharacteristic in_trc = in->color_trc; - ret = d3d12_scale_get_colorspace(ctx, s->input_is_rgb, 0, &in_spc, &in_range, - &in_primaries, &in_trc, in->chroma_location, - AVCOL_PRI_UNSPECIFIED, &s->input_colorspace); + ret = ff_d3d12_filter_get_colorspace(ctx, s->input_is_rgb, 0, &in_spc, &in_range, + &in_primaries, &in_trc, in->chroma_location, + AVCOL_PRI_UNSPECIFIED, &s->input_colorspace); if (ret < 0) goto fail; @@ -1231,7 +431,7 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) /* Output color properties: options override, otherwise inherit * from the input. RGB output defaults to full range. */ - out_map = d3d12_scale_map_from_dxgi(s->output_format); + out_map = ff_d3d12_filter_format_from_dxgi(s->output_format); s->output_is_rgb = out_map ? out_map->is_rgb : 0; const AVPixFmtDescriptor *out_desc = out_map ? av_pix_fmt_desc_get(out_map->pix_fmt) : NULL; s->output_has_alpha = out_desc && (out_desc->flags & AV_PIX_FMT_FLAG_ALPHA); @@ -1262,9 +462,9 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) else out_range = s->output_is_rgb ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; - ret = d3d12_scale_get_colorspace(ctx, s->output_is_rgb, 1, &out_spc, &out_range, - &out_primaries, &out_trc, in->chroma_location, - input_601_hint, &s->output_colorspace); + ret = ff_d3d12_filter_get_colorspace(ctx, s->output_is_rgb, 1, &out_spc, &out_range, + &out_primaries, &out_trc, in->chroma_location, + input_601_hint, &s->output_colorspace); if (ret < 0) goto fail; @@ -1316,7 +516,7 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) goto fail; } if (!s->output_is_rgb) { - enum AVChromaLocation out_chroma_loc = d3d12_scale_dxgi_chroma_location(s->output_colorspace); + enum AVChromaLocation out_chroma_loc = ff_d3d12_filter_chroma_location(s->output_colorspace); if (outlink->chroma_location != AVCHROMA_LOC_UNSPECIFIED && outlink->chroma_location != out_chroma_loc) { av_log(ctx, AV_LOG_ERROR, @@ -1327,7 +527,7 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) } } - s->input_framerate = get_input_framerate(ctx, inlink, in); + s->input_framerate = ff_d3d12_filter_get_input_framerate(ctx, inlink, in); av_log(ctx, AV_LOG_VERBOSE, "Input format: %dx%d, DXGI format: %d, colorspace: %d -> " @@ -1374,62 +574,19 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) goto fail; } - ret = scale_d3d12_get_valid_command_allocator(s, ctx, &command_allocator); + ret = ff_d3d12_vpp_begin(ctx, &s->vpp); if (ret < 0) goto fail; - hr = ID3D12CommandAllocator_Reset(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, 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 force_reconfigure; - } - - D3D12_RESOURCE_DESC input_res_desc, output_res_desc; - input_resource->lpVtbl->GetDesc(input_resource, &input_res_desc); - output_resource->lpVtbl->GetDesc(output_resource, &output_res_desc); - - int input_nb_planes = s->input_is_rgb ? 1 : 2; - int output_nb_planes = s->output_is_rgb ? 1 : 2; - D3D12_RESOURCE_BARRIER barriers[4]; int nb_barriers = 0; - for (int p = 0; p < input_nb_planes; p++) { - D3D12_RESOURCE_BARRIER b = { - .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, - .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, - .Transition = { - .pResource = input_resource, - .Subresource = input_frame->subresource_index + p * input_res_desc.DepthOrArraySize, - .StateBefore = D3D12_RESOURCE_STATE_COMMON, - .StateAfter = D3D12_RESOURCE_STATE_VIDEO_PROCESS_READ - } - }; - barriers[nb_barriers++] = b; - } - for (int p = 0; p < output_nb_planes; p++) { - D3D12_RESOURCE_BARRIER b = { - .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, - .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, - .Transition = { - .pResource = output_resource, - .Subresource = output_frame->subresource_index + p * output_res_desc.DepthOrArraySize, - .StateBefore = D3D12_RESOURCE_STATE_COMMON, - .StateAfter = D3D12_RESOURCE_STATE_VIDEO_PROCESS_WRITE - } - }; - barriers[nb_barriers++] = b; - } + ff_d3d12_filter_add_frame_barriers(barriers, &nb_barriers, input_frame, s->input_is_rgb ? 1 : 2, + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VIDEO_PROCESS_READ); + ff_d3d12_filter_add_frame_barriers(barriers, &nb_barriers, output_frame, s->output_is_rgb ? 1 : 2, + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VIDEO_PROCESS_WRITE); - ID3D12VideoProcessCommandList_ResourceBarrier(s->command_list, nb_barriers, barriers); + ID3D12VideoProcessCommandList_ResourceBarrier(s->vpp.cmd.list.video_process, nb_barriers, barriers); D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS input_args = {0}; @@ -1463,8 +620,8 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) output_args.TargetRectangle.bottom = s->height; ID3D12VideoProcessCommandList_ProcessFrames( - s->command_list, - s->video_processor, + s->vpp.cmd.list.video_process, + s->vpp.video_processor, &output_args, 1, &input_args @@ -1473,84 +630,14 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) for (int i = 0; i < nb_barriers; i++) { FFSWAP(D3D12_RESOURCE_STATES, barriers[i].Transition.StateBefore, barriers[i].Transition.StateAfter); } - ID3D12VideoProcessCommandList_ResourceBarrier(s->command_list, nb_barriers, barriers); + ID3D12VideoProcessCommandList_ResourceBarrier(s->vpp.cmd.list.video_process, nb_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 force_reconfigure; - } - - int wait_enqueued = 0; - - /* Wait for the input frame's fence so prior GPU work on this - * surface completes before the video processor reads it. */ - if (input_frame->sync_ctx.fence && input_frame->sync_ctx.fence_value > 0) { - UINT64 completed = ID3D12Fence_GetCompletedValue(input_frame->sync_ctx.fence); - if (completed < input_frame->sync_ctx.fence_value) { - hr = ID3D12CommandQueue_Wait(s->command_queue, input_frame->sync_ctx.fence, input_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_enqueued = 1; - s->gpu_work_submitted = 1; - } - } - - if (output_frame->sync_ctx.fence && output_frame->sync_ctx.fence_value > 0) { - UINT64 completed = ID3D12Fence_GetCompletedValue(output_frame->sync_ctx.fence); - if (completed < output_frame->sync_ctx.fence_value) { - hr = ID3D12CommandQueue_Wait(s->command_queue, output_frame->sync_ctx.fence, output_frame->sync_ctx.fence_value); - if (FAILED(hr)) { - av_log(ctx, AV_LOG_ERROR, "Failed to wait for output frame fence: HRESULT 0x%lX\n", hr); - ret = AVERROR_EXTERNAL; - if (wait_enqueued) - goto submit_fail; - goto fail; - } - s->gpu_work_submitted = 1; - } - } - - ID3D12CommandList *cmd_lists[] = { (ID3D12CommandList *)s->command_list }; - ID3D12CommandQueue_ExecuteCommandLists(s->command_queue, 1, cmd_lists); - s->gpu_work_submitted = 1; - - UINT64 next_output_fence_value = output_frame->sync_ctx.fence_value + 1; - UINT64 next_private_fence_value = s->fence_value; - - hr = ID3D12CommandQueue_Signal(s->command_queue, output_frame->sync_ctx.fence, - next_output_fence_value); - if (FAILED(hr)) { - av_log(ctx, AV_LOG_ERROR, "Failed to signal output frame fence: HRESULT 0x%lX\n", hr); - ret = AVERROR_EXTERNAL; - goto submit_fail; - } - output_frame->sync_ctx.fence_value = next_output_fence_value; - - hr = ID3D12CommandQueue_Signal(s->command_queue, s->fence, next_private_fence_value); - if (FAILED(hr)) { - av_log(ctx, AV_LOG_ERROR, "Failed to signal fence: HRESULT 0x%lX\n", hr); - ret = AVERROR_EXTERNAL; - goto submit_fail; - } - - s->fence_value++; - - AVFrame *input_ref = av_frame_clone(in); - if (!input_ref) { - ret = AVERROR(ENOMEM); - goto submit_fail; - } - - ret = scale_d3d12_discard_command_allocator(s, ctx, command_allocator, next_private_fence_value, - input_ref); - command_allocator = NULL; - if (ret < 0) + ret = ff_d3d12_vpp_submit(ctx, &s->vpp, &in, 1, out, &frames_in_use); + if (ret < 0) { + if (frames_in_use) + return ret; goto fail; + } ret = av_frame_copy_props(out, in); if (ret < 0) { @@ -1592,69 +679,14 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in) if (s->output_is_rgb) { out->chroma_location = AVCHROMA_LOC_UNSPECIFIED; } else { - out->chroma_location = d3d12_scale_dxgi_chroma_location(s->output_colorspace); + out->chroma_location = ff_d3d12_filter_chroma_location(s->output_colorspace); } av_frame_free(&in); return ff_filter_frame(outlink, out); -submit_fail: - removed_reason = ID3D12Device_GetDeviceRemovedReason(s->device); - gpu_confirmed_done = FAILED(removed_reason); - - if (gpu_confirmed_done) { - av_log(ctx, AV_LOG_ERROR, - "D3D12 device removed (HRESULT 0x%lX); frame " - "resources are safe to release\n", removed_reason); - } else if (s->command_queue && s->fence && s->fence_event) { - UINT64 last_resort = s->fence_value + 1; - if (SUCCEEDED(ID3D12CommandQueue_Signal(s->command_queue, s->fence, last_resort))) { - UINT64 completed; - - if (last_resort >= s->fence_value) - s->fence_value = last_resort + 1; - - completed = ID3D12Fence_GetCompletedValue(s->fence); - if (completed >= last_resort) { - gpu_confirmed_done = 1; - } else { - gpu_confirmed_done = scale_d3d12_wait_fence_exact(s->fence, last_resort, - s->fence_event, 5000); - if (!gpu_confirmed_done) - av_log(ctx, AV_LOG_ERROR, - "Post-failure GPU wait timed out; frame " - "resources cannot be safely recycled\n"); - } - } else { - av_log(ctx, AV_LOG_ERROR, - "Best-effort post-failure GPU sync Signal also " - "failed; frame resources cannot be safely " - "recycled\n"); - } - } else { - av_log(ctx, AV_LOG_ERROR, - "No fence/queue for post-failure GPU sync; frame " - "resources cannot be safely recycled\n"); - } - - if (!gpu_confirmed_done) { - av_log(ctx, AV_LOG_ERROR, - "Abandoning this frame's input/output surfaces and " - "command allocator without releasing them\n"); - return ret; - } - - goto fail; - -force_reconfigure: - s->configured = 0; - if (!release_d3d12_resources(s, ctx)) - command_allocator = NULL; - fail: - if (command_allocator) - ID3D12CommandAllocator_Release(command_allocator); av_frame_free(&in); av_frame_free(&out); return ret; @@ -1669,7 +701,7 @@ static int scale_d3d12_config_props(AVFilterLink *outlink) FilterLink *outl = ff_filter_link(outlink); int ret; - if (!release_d3d12_resources(s, ctx)) { + if (ff_d3d12_vpp_uninit(ctx, &s->vpp) < 0) { av_log(ctx, AV_LOG_ERROR, "Cannot (re)configure scale_d3d12: a prior configuration's " "D3D12 resources could not be confirmed released\n"); @@ -1725,11 +757,6 @@ static int scale_d3d12_config_props(AVFilterLink *outlink) return AVERROR(EINVAL); } - 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; AVHWFramesContext *in_frames_ctx = (AVHWFramesContext *)inl->hw_frames_ctx->data; enum AVPixelFormat resolved_format = s->format; @@ -1739,55 +766,34 @@ static int scale_d3d12_config_props(AVFilterLink *outlink) av_log(ctx, AV_LOG_VERBOSE, "D3D12 scale output format not specified, using input format: %s\n", av_get_pix_fmt_name(resolved_format)); } - frames_ctx->sw_format = resolved_format; /* Set output format based on sw_format */ - const D3D12ScaleFormatMap *out_map = d3d12_scale_map_from_pixfmt(frames_ctx->sw_format); + const D3D12FilterFormat *out_map = ff_d3d12_filter_format_from_pix_fmt(resolved_format); if (!out_map) { av_log(ctx, AV_LOG_ERROR, "Unsupported output format: %s\n", - av_get_pix_fmt_name(frames_ctx->sw_format)); - av_buffer_unref(&s->hw_frames_ctx_out); + av_get_pix_fmt_name(resolved_format)); return AVERROR(EINVAL); } s->output_format = out_map->dxgi_format; s->output_is_rgb = out_map->is_rgb; - const D3D12ScaleFormatMap *in_map = d3d12_scale_map_from_pixfmt(in_frames_ctx->sw_format); + const D3D12FilterFormat *in_map = ff_d3d12_filter_format_from_pix_fmt(in_frames_ctx->sw_format); int input_is_rgb = in_map ? in_map->is_rgb : 0; if (s->color_primaries != AVCOL_PRI_UNSPECIFIED || s->color_transfer != AVCOL_TRC_UNSPECIFIED || s->color_matrix != AVCOL_SPC_UNSPECIFIED || input_is_rgb != s->output_is_rgb || - frames_ctx->sw_format == AV_PIX_FMT_RGBAF16 || + resolved_format == AV_PIX_FMT_RGBAF16 || (input_is_rgb && s->color_range != AVCOL_RANGE_UNSPECIFIED)) av_frame_side_data_remove_by_props(&outlink->side_data, &outlink->nb_side_data, AV_SIDE_DATA_PROP_COLOR_DEPENDENT); - frames_ctx->width = s->width; - frames_ctx->height = s->height; - frames_ctx->format = AV_PIX_FMT_D3D12; - frames_ctx->initial_pool_size = 4; - - if (ctx->extra_hw_frames > 0) - frames_ctx->initial_pool_size += ctx->extra_hw_frames; - - AVD3D12VAFramesContext *frames_hwctx = frames_ctx->hwctx; - - /* - * Set D3D12 resource flags for video processing - * ALLOW_RENDER_TARGET is needed for video processor output - */ - frames_hwctx->format = s->output_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); + ret = ff_d3d12_filter_init_output_frames(ctx, s->hw_device_ctx, resolved_format, + s->width, s->height, 4, &s->hw_frames_ctx_out); + if (ret < 0) return ret; - } outl->hw_frames_ctx = av_buffer_ref(s->hw_frames_ctx_out); if (!outl->hw_frames_ctx) @@ -1802,7 +808,7 @@ static int scale_d3d12_config_props(AVFilterLink *outlink) static av_cold void scale_d3d12_uninit(AVFilterContext *ctx) { ScaleD3D12Context *s = ctx->priv; - if (release_d3d12_resources(s, ctx)) { + if (ff_d3d12_vpp_uninit(ctx, &s->vpp) >= 0) { av_buffer_unref(&s->hw_frames_ctx_out); av_buffer_unref(&s->hw_device_ctx); } -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
