Commit 5d5a38a7e4 for ffmpeg
commit 5d5a38a7e46be296eab75982c0986054f731e19e
Author: Steven Xiao <steven.xiao@amd.com>
Date: Thu Oct 29 07:38:00 2026 -0400
avfilter/vf_scale_d3d12: add RGB formats and color space conversion
Extend scale_d3d12 from NV12/P010 resizing to full pixel format and
color space conversion, matching what the D3D12 video processor is
actually capable of.
- Support converting to/from the semi-planar YUV formats (nv12, p010,
p012) and the packed RGB formats (bgra, rgba, x2bgr10, rgbaf16),
when the hardware supports it.
- Add out_color_matrix, out_range, out_color_primaries and
out_color_transfer options.
- Recalculate sample_aspect_ratio and drop size-dependent side data
on resize.
- Harden GPU teardown/reconfiguration.
Example usage:
Convert a d3d12 NV12 surface to BGRA:
scale_d3d12=format=bgra
Convert an HDR10 (P010) surface to 10-bit RGB, without altering
its HDR color properties:
scale_d3d12=format=x2bgr10
Scale and convert to full-range BGRA:
scale_d3d12=format=bgra:out_range=full
Signed-off-by: younengxiao <steven.xiao@amd.com>
diff --git a/doc/filters.texi b/doc/filters.texi
index e2f2d38f67..b57a5e8a00 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -22392,6 +22392,146 @@ If the specified expression is not valid, it is kept at its current
value.
@end table
+@section scale_d3d12
+
+Scale (resize) and convert the pixel format of the input video using
+Direct3D 12 hardware-accelerated video processing
+(@code{ID3D12VideoProcessor}). It requires the input to be in @code{d3d12}
+hardware pixel format.
+
+Setting the output width and height works in the same way as for the
+@ref{scale} filter. @code{sample_aspect_ratio} is recalculated for the new
+dimensions the same way @ref{scale} does it, and side data whose values
+are expressed in pixel coordinates of the old size (motion vectors,
+detection bounding boxes, ROI, ...) is removed when the size actually
+changes, also the same way @ref{scale} does it.
+
+Which input/output pixel format and color space combinations are actually
+supported depends on the GPU and driver. The filter first rejects color
+descriptions it cannot represent using DXGI. For representable
+combinations, support is queried from the GPU and driver. If that
+capability query reports the conversion unsupported, @code{-v debug}
+prints a probe of alternative output combinations.
+
+The filter accepts the following options:
+@table @option
+@item w
+@item h
+Set the output video dimension expression. Default value is the input
+dimension.
+
+Allows for the same expressions as the @ref{scale} filter.
+
+@item format
+Controls the output pixel format. By default, or if none is specified, the
+input pixel format is used.
+
+Besides converting between the semi-planar YUV formats @code{nv12},
+@code{p010} and @code{p012}, this filter can also convert to the packed
+RGB formats @code{bgra}, @code{rgba}, @code{x2bgr10} and @code{rgbaf16},
+when supported by the hardware.
+
+@item out_color_matrix
+Set the output colorspace matrix. This only affects YUV output formats
+and accepts the same values as the @option{space} option of the
+@code{colorspace} filter, e.g. @code{smpte170m}, @code{bt709},
+@code{bt2020nc}; only those exact names are recognized, not other
+common aliases (e.g. @code{bt2020nc}, not @code{bt2020ncl}). If
+@option{out_color_matrix} is not specified and @option{out_color_primaries}
+is explicitly set, the output matrix is derived from the requested
+primaries. Otherwise, the input matrix is inherited; if it is
+unspecified, the matrix is derived from the input primaries.
+
+@item out_range
+Set the output color range. It accepts the following values:
+
+@table @samp
+@item tv, mpeg, limited
+Limited range.
+
+@item pc, jpeg, full
+Full range.
+@end table
+
+If not specified, it defaults to the input range, except that a
+@option{out_color_transfer} of @code{linear} (explicit or defaulted,
+see below) always selects @code{full} range. If the range is still
+unspecified at that point, it defaults to @code{limited} for YUV
+output and @code{full} for RGB output.
+
+@item out_color_primaries
+Set the output color primaries. It accepts the same values as the
+@option{primaries} option of the @code{colorspace} filter, e.g.
+@code{bt709}, @code{bt2020}. If not specified, it defaults to the input
+primaries.
+
+@item out_color_transfer
+Set the output color transfer characteristics. It accepts the same
+values as the @option{trc} option of the @code{colorspace} filter, e.g.
+@code{bt709}, @code{smpte2084}, @code{arib-std-b67}; only those exact
+names are recognized, not other common aliases (e.g.
+@code{iec61966-2-1}, not @code{srgb}; @code{bt470bg}, not
+@code{gamma28}). If not specified, it defaults to the input transfer
+characteristics, except that @code{rgbaf16} output defaults to
+@code{linear} when both this option and the input transfer
+characteristics are unspecified.
+
+@end table
+
+The video processor and output color properties are configured from
+the first frame and reused for subsequent frames. Changes in frame
+dimensions require filter-graph reinitialization.
+
+If this filter changes the output color primaries and/or transfer
+characteristics relative to the input, side data describing those
+properties (mastering display metadata, HDR10+/Dolby Vision metadata,
+ICC profile, ...) is removed from the output frame.
+
+Alpha is preserved for RGB formats that have it (@code{bgra},
+@code{rgba}, @code{rgbaf16}); other formats, and devices that cannot
+pass alpha through, produce opaque alpha instead. A premultiplied-alpha
+input is rejected if the requested conversion cannot preserve it
+correctly (e.g. it also changes color space); unpremultiply the input
+first in that case.
+
+YUV output is retagged with the chroma sample location the selected
+output color space actually uses, which depends on the matrix, range
+and transfer characteristic (e.g. @code{topleft} for HLG, @code{left}
+for BT.601/BT.709); RGB output is always tagged @code{unspecified}. An
+input chroma siting the chosen input color space cannot represent is
+rejected.
+
+@subsection Examples
+
+@itemize
+@item
+Scale a @code{d3d12} NV12 surface to 1280x720, keeping the pixel format:
+@example
+scale_d3d12=w=1280:h=720
+@end example
+
+@item
+Convert a @code{d3d12} NV12 surface to BGRA, so it can be downloaded and
+encoded or displayed without a software pixel format conversion:
+@example
+scale_d3d12=format=bgra
+@end example
+
+@item
+Convert a 10-bit HDR10 (P010) surface to 10-bit RGB, without altering
+its HDR color properties:
+@example
+scale_d3d12=format=x2bgr10
+@end example
+
+@item
+Scale and convert to full-range BGRA, for example before @code{hwdownload}
+feeds a renderer that expects full-range RGB:
+@example
+scale_d3d12=format=bgra:out_range=full
+@end example
+@end itemize
+
@section scharr
Apply scharr operator to input video stream.
diff --git a/libavfilter/vf_scale_d3d12.c b/libavfilter/vf_scale_d3d12.c
index 7bddda6552..cadd48e5a0 100644
--- a/libavfilter/vf_scale_d3d12.c
+++ b/libavfilter/vf_scale_d3d12.c
@@ -19,6 +19,7 @@
#define COBJMACROS
#include "libavutil/fifo.h"
+#include "libavutil/frame.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "compat/w32dlfcn.h"
@@ -28,20 +29,64 @@
#include "libavutil/hwcontext_d3d12va_internal.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;
char *h_expr;
enum AVPixelFormat format;
+ char *color_matrix_string;
+ char *color_transfer_string;
+ char *color_primaries_string;
+ int color_range;
+ enum AVColorSpace color_matrix;
+ enum AVColorTransferCharacteristic color_transfer;
+ enum AVColorPrimaries color_primaries;
+
/* D3D12 objects */
ID3D12Device *device;
ID3D12VideoDevice *video_device;
@@ -51,6 +96,9 @@ typedef struct ScaleD3D12Context {
AVFifo *allocator_queue;
+ int configured;
+ int gpu_work_submitted;
+
/* Synchronization */
ID3D12Fence *fence;
UINT64 fence_value;
@@ -65,19 +113,90 @@ typedef struct ScaleD3D12Context {
int input_width, input_height;
DXGI_FORMAT input_format;
DXGI_FORMAT output_format;
+ int input_is_rgb;
+ int output_is_rgb;
+ int input_has_alpha;
+ int output_has_alpha;
+ int alpha_passthrough;
/* Color space and frame rate */
DXGI_COLOR_SPACE_TYPE input_colorspace;
+ DXGI_COLOR_SPACE_TYPE output_colorspace;
AVRational input_framerate;
+ /* AVFrame-facing output color properties, derived once from the first
+ * frame plus the out_* options and then reused for every frame. */
+ enum AVColorSpace output_av_colorspace;
+ enum AVColorRange output_av_color_range;
+ enum AVColorPrimaries output_av_color_primaries;
+ enum AVColorTransferCharacteristic output_av_color_trc;
+
+ enum AVColorPrimaries input_av_color_primaries;
+ enum AVColorTransferCharacteristic input_av_color_trc;
+
/* Video processor capabilities */
D3D12_FEATURE_DATA_VIDEO_PROCESS_SUPPORT process_support;
} ScaleD3D12Context;
static av_cold int scale_d3d12_init(AVFilterContext *ctx) {
+ ScaleD3D12Context *s = ctx->priv;
+
+#define STRING_OPTION(var_name, func_name, opt_name, default_value) do { \
+ if (s->var_name ## _string) { \
+ int var = av_ ## func_name ## _from_name(s->var_name ## _string); \
+ if (var < 0) { \
+ av_log(ctx, AV_LOG_ERROR, "Invalid %s.\n", opt_name); \
+ return AVERROR(EINVAL); \
+ } \
+ s->var_name = var; \
+ } else { \
+ s->var_name = default_value; \
+ } \
+ } while (0)
+
+ STRING_OPTION(color_matrix, color_space, "out_color_matrix", AVCOL_SPC_UNSPECIFIED);
+ STRING_OPTION(color_transfer, color_transfer, "out_color_transfer", AVCOL_TRC_UNSPECIFIED);
+ STRING_OPTION(color_primaries, color_primaries, "out_color_primaries", AVCOL_PRI_UNSPECIFIED);
+
+#undef STRING_OPTION
+
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)
{
@@ -89,6 +208,7 @@ static int scale_d3d12_get_valid_command_allocator(ScaleD3D12Context *s, AVFilte
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;
}
}
@@ -103,37 +223,71 @@ static int scale_d3d12_get_valid_command_allocator(ScaleD3D12Context *s, AVFilte
return 0;
}
-static int scale_d3d12_discard_command_allocator(ScaleD3D12Context *s, ID3D12CommandAllocator *pAllocator,
- UINT64 fence_value)
+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);
- 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);
- return 0;
+ 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 void release_d3d12_resources(ScaleD3D12Context *s) {
- UINT64 fence_value;
- HRESULT hr;
- D3D12ScaleCommandAllocator allocator;
+static int release_d3d12_resources(ScaleD3D12Context *s, AVFilterContext *ctx) {
+ int gpu_confirmed_done = 1;
- /* Wait for all GPU operations to complete before releasing resources */
- if (s->command_queue && s->fence && s->fence_event) {
- fence_value = s->fence_value - 1;
- hr = ID3D12CommandQueue_Signal(s->command_queue, s->fence, fence_value);
- if (SUCCEEDED(hr)) {
- UINT64 completed = ID3D12Fence_GetCompletedValue(s->fence);
- if (completed < fence_value) {
- hr = ID3D12Fence_SetEventOnCompletion(s->fence, fence_value, s->fence_event);
- if (SUCCEEDED(hr)) {
- WaitForSingleObject(s->fence_event, INFINITE);
- }
- }
+ 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) {
@@ -152,8 +306,11 @@ static void release_d3d12_resources(ScaleD3D12Context *s) {
}
if (s->allocator_queue) {
- while (av_fifo_read(s->allocator_queue, &allocator, 1) >= 0)
+ 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);
}
@@ -171,54 +328,552 @@ static void release_d3d12_resources(ScaleD3D12Context *s) {
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) {
+ case AVCOL_TRC_BT709:
+ case AVCOL_TRC_SMPTE170M:
+ case AVCOL_TRC_BT2020_10:
+ case AVCOL_TRC_BT2020_12:
+ return 0; /* trc_bt709 */
+
+ case AVCOL_TRC_GAMMA22:
+ return 1; /* trc_gamma22 */
+
+ case AVCOL_TRC_GAMMA28:
+ return 2; /* trc_gamma28 */
+
+ case AVCOL_TRC_SMPTE240M:
+ return 3; /* trc_smpte240M */
+
+ case AVCOL_TRC_IEC61966_2_1:
+ return 4; /* trc_iec61966_2_1 (sRGB) */
+
+ default:
+ return -1;
+ }
+}
+
+static int d3d12_scale_finalize_output_trc(AVFilterContext *ctx,
+ DXGI_COLOR_SPACE_TYPE input_space,
+ DXGI_COLOR_SPACE_TYPE output_space,
+ int explicit_request,
+ enum AVColorTransferCharacteristic input_trc,
+ enum AVColorTransferCharacteristic *trc)
+{
+ enum AVColorTransferCharacteristic canonical =
+ (output_space == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) ?
+ AVCOL_TRC_IEC61966_2_1 : AVCOL_TRC_BT709;
+ enum AVColorTransferCharacteristic canonical_input =
+ (input_trc == AVCOL_TRC_SMPTE2084 ||
+ input_trc == AVCOL_TRC_ARIB_STD_B67 ||
+ input_trc == AVCOL_TRC_LINEAR) ? input_trc :
+ (input_space == DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) ?
+ AVCOL_TRC_IEC61966_2_1 : AVCOL_TRC_BT709;
+
+ if (canonical_input != canonical) {
+ if (*trc != canonical) {
+ const char *old_name = av_color_transfer_name(*trc);
+ const char *new_name = av_color_transfer_name(canonical);
+ av_log(ctx, AV_LOG_WARNING,
+ "No exact DXGI color space; retagging transfer "
+ "characteristic from %s to %s\n",
+ old_name ? old_name : "unknown", new_name ? new_name : "unknown");
+ *trc = canonical;
+ }
+
+ return 0;
+ }
+
+ if (!explicit_request)
+ return 0;
+
+ if (d3d12_scale_trc_group(*trc) == d3d12_scale_trc_group(input_trc))
+ return 0;
+
+ const char *req_name = av_color_transfer_name(*trc);
+
+ if (input_trc == AVCOL_TRC_UNSPECIFIED) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Cannot verify requested transfer characteristic (%s); "
+ "input's curve is unspecified/unknown\n",
+ req_name ? req_name : "requested value");
+ } else {
+ const char *old_name = av_color_transfer_name(input_trc);
+ av_log(ctx, AV_LOG_ERROR,
+ "Requested transfer characteristic (%s) differs from "
+ "input's (%s); same DXGI color space means no curve "
+ "change applies\n",
+ req_name ? req_name : "requested value",
+ old_name ? old_name : "transfer characteristic");
+ }
+
+ 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 DXGI_COLOR_SPACE_TYPE get_dxgi_colorspace(enum AVColorSpace colorspace, enum AVColorTransferCharacteristic trc, int is_10bit)
+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)
{
- /* Map FFmpeg color space to DXGI color space */
- if (is_10bit) {
- /* 10-bit formats (P010) */
- if (colorspace == AVCOL_SPC_BT2020_NCL || colorspace == AVCOL_SPC_BT2020_CL) {
- if (trc == AVCOL_TRC_SMPTE2084) {
- return DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020; ///< HDR10
- } else if (trc == AVCOL_TRC_ARIB_STD_B67) {
- return DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020; ///< HLG
+ 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 {
- return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020;
+ *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 {
- return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709; ///< Rec.709 10-bit
+ 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;
}
- } else {
- /* 8-bit formats (NV12) */
- if (colorspace == AVCOL_SPC_BT2020_NCL || colorspace == AVCOL_SPC_BT2020_CL) {
- return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020;
- } else if (colorspace == AVCOL_SPC_BT470BG || colorspace == AVCOL_SPC_SMPTE170M) {
- return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601;
+
+ 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 {
- return DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709; ///< Default to Rec.709
+ 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 (in->duration > 0 && inlink->time_base.num > 0 && inlink->time_base.den > 0) {
- /*
- * Calculate framerate from frame duration and timebase
- * framerate = 1 / (duration * timebase)
- */
+ 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 if (inlink->time_base.num > 0 && inlink->time_base.den > 0) {
- /* Estimate from timebase (inverse of timebase is often the framerate) */
- framerate.num = inlink->time_base.den;
- framerate.den = inlink->time_base.num;
} else {
- /* Default to 30fps if framerate cannot be determined */
framerate.num = 30;
framerate.den = 1;
av_log(ctx, AV_LOG_WARNING, "Input framerate not determinable, defaulting to 30fps\n");
@@ -232,6 +887,15 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext
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);
@@ -247,6 +911,7 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext
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;
}
@@ -260,6 +925,8 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext
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;
}
@@ -275,7 +942,7 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext
s->process_support.InputStereoFormat = D3D12_VIDEO_FRAME_STEREO_FORMAT_NONE;
s->process_support.OutputFormat.Format = s->output_format;
- s->process_support.OutputFormat.ColorSpace = s->input_colorspace;
+ s->process_support.OutputFormat.ColorSpace = s->output_colorspace;
s->process_support.OutputFrameRate.Numerator = s->input_framerate.num;
s->process_support.OutputFrameRate.Denominator = s->input_framerate.den;
s->process_support.OutputStereoFormat = D3D12_VIDEO_FRAME_STEREO_FORMAT_NONE;
@@ -289,18 +956,42 @@ 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);
+
return AVERROR_EXTERNAL;
}
if (!(s->process_support.SupportFlags & D3D12_VIDEO_PROCESS_SUPPORT_FLAG_SUPPORTED)) {
- av_log(ctx, AV_LOG_ERROR, "Video process configuration not supported by hardware\n");
- return AVERROR_EXTERNAL;
+ 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),
+ 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),
+ s->output_colorspace);
+ scale_d3d12_report_output_support(s, ctx);
+ release_d3d12_resources(s, ctx);
+
+ return AVERROR(ENOSYS);
}
+ int want_alpha_passthrough = s->input_has_alpha && s->output_has_alpha;
+ if (want_alpha_passthrough &&
+ !(s->process_support.FeatureSupport & D3D12_VIDEO_PROCESS_FEATURE_FLAG_ALPHA_FILL)) {
+ av_log(ctx, AV_LOG_WARNING,
+ "Device lacks ALPHA_FILL support; output alpha will be "
+ "forced opaque instead of preserved\n");
+ want_alpha_passthrough = 0;
+ }
+ s->alpha_passthrough = want_alpha_passthrough;
+
D3D12_VIDEO_PROCESS_OUTPUT_STREAM_DESC processor_output_desc = {
.Format = s->output_format,
- .ColorSpace = s->input_colorspace,
- .AlphaFillMode = D3D12_VIDEO_PROCESS_ALPHA_FILL_MODE_OPAQUE,
+ .ColorSpace = s->output_colorspace,
+ .AlphaFillMode = want_alpha_passthrough ? D3D12_VIDEO_PROCESS_ALPHA_FILL_MODE_SOURCE_STREAM
+ : D3D12_VIDEO_PROCESS_ALPHA_FILL_MODE_OPAQUE,
.AlphaFillModeSourceStreamIndex = 0,
.BackgroundColor = { 0.0f, 0.0f, 0.0f, 1.0f },
.FrameRate = { s->input_framerate.num, s->input_framerate.den },
@@ -310,8 +1001,8 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext
D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC processor_input_desc = {
.Format = s->input_format,
.ColorSpace = s->input_colorspace,
- .SourceAspectRatio = { s->input_width, s->input_height },
- .DestinationAspectRatio = { s->width, s->height },
+ .SourceAspectRatio = { 1, 1 },
+ .DestinationAspectRatio = { 1, 1 },
.FrameRate = { s->input_framerate.num, s->input_framerate.den },
.StereoFormat = D3D12_VIDEO_FRAME_STEREO_FORMAT_NONE,
.FieldType = D3D12_VIDEO_FIELD_TYPE_NONE,
@@ -337,15 +1028,6 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext
.EnableAutoProcessing = FALSE,
};
- /* If pixel aspect ratio adjustment is not supported, set to 1:1 and warn */
- if (!(s->process_support.FeatureSupport & D3D12_VIDEO_PROCESS_FEATURE_FLAG_PIXEL_ASPECT_RATIO)) {
- processor_input_desc.SourceAspectRatio.Numerator = 1;
- processor_input_desc.SourceAspectRatio.Denominator = 1;
- processor_input_desc.DestinationAspectRatio.Numerator = 1;
- processor_input_desc.DestinationAspectRatio.Denominator = 1;
- av_log(ctx, AV_LOG_WARNING, "Pixel aspect ratio adjustment not supported by hardware\n");
- }
-
hr = ID3D12VideoDevice_CreateVideoProcessor(
s->video_device,
0,
@@ -358,12 +1040,14 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext
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;
}
@@ -371,16 +1055,22 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext
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)
+ 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)
+ if (ret < 0) {
+ release_d3d12_resources(s, ctx);
return ret;
+ }
hr = ID3D12Device_CreateCommandList(
s->device,
@@ -395,14 +1085,27 @@ static int scale_d3d12_configure_processor(ScaleD3D12Context *s, AVFilterContext
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;
}
- ID3D12VideoProcessCommandList_Close(s->command_list);
+ 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);
- ret = scale_d3d12_discard_command_allocator(s, command_allocator, 0);
- if (ret < 0)
+ 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;
@@ -417,6 +1120,8 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
int ret = 0;
HRESULT hr;
ID3D12CommandAllocator *command_allocator = NULL;
+ HRESULT removed_reason;
+ int gpu_confirmed_done;
if (!in) {
av_log(ctx, AV_LOG_ERROR, "Null input frame\n");
@@ -426,6 +1131,7 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
if (!in->hw_frames_ctx) {
av_log(ctx, AV_LOG_ERROR, "No hardware frames context in input frame\n");
av_frame_free(&in);
+
return AVERROR(EINVAL);
}
@@ -434,6 +1140,7 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
if (!s->hw_device_ctx) {
av_log(ctx, AV_LOG_ERROR, "Filter hardware device context is uninitialized\n");
av_frame_free(&in);
+
return AVERROR(EINVAL);
}
@@ -443,6 +1150,18 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
if (input_device_ctx->type != filter_device_ctx->type) {
av_log(ctx, AV_LOG_ERROR, "Mismatch between input and filter hardware device types\n");
av_frame_free(&in);
+
+ return AVERROR(EINVAL);
+ }
+
+ AVD3D12VADeviceContext *input_d3d12_hwctx = (AVD3D12VADeviceContext *)input_device_ctx->hwctx;
+ AVD3D12VADeviceContext *filter_d3d12_hwctx = (AVD3D12VADeviceContext *)filter_device_ctx->hwctx;
+ if (input_d3d12_hwctx->device != filter_d3d12_hwctx->device) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Input frame's hw_frames_ctx belongs to a different "
+ "ID3D12Device than this filter's own device\n");
+ av_frame_free(&in);
+
return AVERROR(EINVAL);
}
@@ -450,6 +1169,7 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
if (!out) {
av_log(ctx, AV_LOG_ERROR, "Failed to allocate output frame\n");
av_frame_free(&in);
+
return AVERROR(ENOMEM);
}
@@ -459,37 +1179,161 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
goto fail;
}
- if (!s->video_processor) {
+ if (!s->configured) {
AVHWFramesContext *input_frames_ctx = (AVHWFramesContext *)in->hw_frames_ctx->data;
+ const D3D12ScaleFormatMap *in_map, *out_map;
+ enum AVColorSpace out_spc;
+ enum AVColorRange out_range;
+ enum AVColorPrimaries out_primaries;
+ enum AVColorTransferCharacteristic out_trc;
- s->input_width = input_frames_ctx->width;
- s->input_height = input_frames_ctx->height;
+ s->input_width = in->width;
+ s->input_height = in->height;
AVD3D12VAFramesContext *input_hwctx = (AVD3D12VAFramesContext *)input_frames_ctx->hwctx;
s->input_format = input_hwctx->format;
if (s->input_format == DXGI_FORMAT_UNKNOWN) {
- switch (input_frames_ctx->sw_format) {
- case AV_PIX_FMT_NV12:
- s->input_format = DXGI_FORMAT_NV12;
- break;
- case AV_PIX_FMT_P010:
- s->input_format = DXGI_FORMAT_P010;
- break;
- default:
- av_log(ctx, AV_LOG_ERROR, "Unsupported input format\n");
- ret = AVERROR(EINVAL);
- goto fail;
+ in_map = d3d12_scale_map_from_pixfmt(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));
+ ret = AVERROR(EINVAL);
+ goto fail;
}
+ s->input_format = in_map->dxgi_format;
}
- int is_10bit = (s->input_format == DXGI_FORMAT_P010);
- s->input_colorspace = get_dxgi_colorspace(in->colorspace, in->color_trc, is_10bit);
+ in_map = d3d12_scale_map_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);
+
+ enum AVColorRange in_range = in->color_range != AVCOL_RANGE_UNSPECIFIED ? in->color_range
+ : (s->input_is_rgb ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG);
+ enum AVColorSpace in_spc = in->colorspace;
+ 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);
+ if (ret < 0)
+ goto fail;
+
+ s->input_av_color_primaries = in_primaries;
+ s->input_av_color_trc = in_trc;
+
+ enum AVColorPrimaries input_601_hint = AVCOL_PRI_UNSPECIFIED;
+ if (s->input_colorspace == DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601 ||
+ s->input_colorspace == DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601)
+ input_601_hint = in_primaries;
+
+ /* 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);
+ 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);
+
+ out_spc = s->color_matrix != AVCOL_SPC_UNSPECIFIED ? s->color_matrix :
+ s->color_primaries != AVCOL_PRI_UNSPECIFIED ? AVCOL_SPC_UNSPECIFIED :
+ in->colorspace;
+ out_primaries = s->color_primaries != AVCOL_PRI_UNSPECIFIED ? s->color_primaries : in_primaries;
+ out_trc = s->color_transfer != AVCOL_TRC_UNSPECIFIED ? s->color_transfer : in->color_trc;
+ if (s->color_transfer == AVCOL_TRC_UNSPECIFIED && in->color_trc == AVCOL_TRC_UNSPECIFIED &&
+ out_map && out_map->pix_fmt == AV_PIX_FMT_RGBAF16)
+ out_trc = AVCOL_TRC_LINEAR;
+ /* AVCOL_SPC_RGB has no YCbCr matrix of its own, so when converting
+ * an RGB input frame back to a YUV output format Treat it as unspecified
+ * instead */
+ if (!s->output_is_rgb && out_spc == AVCOL_SPC_RGB)
+ out_spc = AVCOL_SPC_UNSPECIFIED;
+
+ if (s->color_range != AVCOL_RANGE_UNSPECIFIED)
+ out_range = s->color_range;
+ else if (out_trc == AVCOL_TRC_LINEAR)
+ /* Linear RGB has no studio-range DXGI color space either default
+ * to full range and let the processor do the limited->full expansion
+ * instead. */
+ out_range = AVCOL_RANGE_JPEG;
+ else if (in->color_range != AVCOL_RANGE_UNSPECIFIED)
+ out_range = in->color_range;
+ 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);
+ if (ret < 0)
+ goto fail;
+
+ if (out_trc != AVCOL_TRC_SMPTE2084 && out_trc != AVCOL_TRC_ARIB_STD_B67 &&
+ out_trc != AVCOL_TRC_LINEAR) {
+ ret = d3d12_scale_finalize_output_trc(ctx, s->input_colorspace, s->output_colorspace,
+ s->color_transfer != AVCOL_TRC_UNSPECIFIED,
+ in->color_trc, &out_trc);
+ if (ret < 0)
+ goto fail;
+ }
+
+ if (s->output_is_rgb)
+ s->output_av_colorspace = AVCOL_SPC_RGB;
+ else if (out_spc != AVCOL_SPC_UNSPECIFIED)
+ s->output_av_colorspace = out_spc;
+ else if (out_primaries == AVCOL_PRI_BT2020)
+ s->output_av_colorspace = AVCOL_SPC_BT2020_NCL;
+ else if (out_primaries == AVCOL_PRI_BT470BG || out_primaries == AVCOL_PRI_SMPTE170M)
+ s->output_av_colorspace = AVCOL_SPC_SMPTE170M;
+ else
+ s->output_av_colorspace = AVCOL_SPC_BT709;
+ s->output_av_color_range = out_range;
+ s->output_av_color_primaries = out_primaries;
+ s->output_av_color_trc = out_trc;
+
+ /* outlink's colorspace/color_range/chroma_location describe a YUV,
+ * subsampled surface; they do not constrain an RGB output, so these
+ * checks only apply when the output is YUV. */
+ if (!s->output_is_rgb && outlink->colorspace != AVCOL_SPC_UNSPECIFIED &&
+ outlink->colorspace != s->output_av_colorspace) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Negotiated output color space (%s) does not match the "
+ "%s produced from this input; set an explicit "
+ "out_color_matrix\n",
+ av_color_space_name(outlink->colorspace),
+ av_color_space_name(s->output_av_colorspace));
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+ if (!s->output_is_rgb && outlink->color_range != AVCOL_RANGE_UNSPECIFIED &&
+ outlink->color_range != s->output_av_color_range) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Negotiated output color range (%s) does not match the "
+ "%s produced from this input; set an explicit out_range\n",
+ av_color_range_name(outlink->color_range),
+ av_color_range_name(s->output_av_color_range));
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+ if (!s->output_is_rgb) {
+ enum AVChromaLocation out_chroma_loc = d3d12_scale_dxgi_chroma_location(s->output_colorspace);
+ if (outlink->chroma_location != AVCHROMA_LOC_UNSPECIFIED &&
+ outlink->chroma_location != out_chroma_loc) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Negotiated output chroma siting does not match the "
+ "siting produced from this input\n");
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+ }
s->input_framerate = get_input_framerate(ctx, inlink, in);
- av_log(ctx, AV_LOG_VERBOSE, "Input format: %dx%d, DXGI format: %d, colorspace: %d, framerate: %d/%d\n",
+ av_log(ctx, AV_LOG_VERBOSE,
+ "Input format: %dx%d, DXGI format: %d, colorspace: %d -> "
+ "Output DXGI format: %d, colorspace: %d, framerate: %d/%d\n",
s->input_width, s->input_height, s->input_format, s->input_colorspace,
+ s->output_format, s->output_colorspace,
s->input_framerate.num, s->input_framerate.den);
ret = scale_d3d12_configure_processor(s, ctx);
@@ -499,6 +1343,19 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
}
}
+ /* alpha_mode is a per-frame AVFrame property, not a fixed property of
+ * the negotiated pixel formats, so this must be checked for every
+ * frame, not just the first one. */
+ if (s->input_has_alpha && in->alpha_mode == AVALPHA_MODE_PREMULTIPLIED &&
+ (!s->output_has_alpha || !s->alpha_passthrough ||
+ s->input_colorspace != s->output_colorspace)) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Input has premultiplied alpha, which this conversion "
+ "cannot preserve; unpremultiply before scale_d3d12\n");
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
AVD3D12VAFrame *input_frame = (AVD3D12VAFrame *)in->data[0];
AVD3D12VAFrame *output_frame = (AVD3D12VAFrame *)out->data[0];
@@ -517,19 +1374,6 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
goto fail;
}
- /* Wait for input frame's fence before accessing 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;
- }
- }
- }
-
ret = scale_d3d12_get_valid_command_allocator(s, ctx, &command_allocator);
if (ret < 0)
goto fail;
@@ -545,37 +1389,55 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
if (FAILED(hr)) {
av_log(ctx, AV_LOG_ERROR, "Failed to reset command list: HRESULT 0x%lX\n", hr);
ret = AVERROR_EXTERNAL;
- goto fail;
+ goto force_reconfigure;
}
- D3D12_RESOURCE_BARRIER barriers[2] = {
- {
+ 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 = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
+ .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 = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
+ .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;
+ }
- ID3D12VideoProcessCommandList_ResourceBarrier(s->command_list, 2, barriers);
+ ID3D12VideoProcessCommandList_ResourceBarrier(s->command_list, nb_barriers, barriers);
D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS input_args = {0};
- input_args.InputStream[0].pTexture2D = input_resource;
+ /* subresource_index is 0 for a plain texture, but for a texture-array
+ * input (AV_D3D12VA_FRAME_FLAG_TEXTURE_ARRAY) it is the real array
+ * slice and must be passed through as-is. */
+ input_args.InputStream[0].pTexture2D = input_resource;
+ input_args.InputStream[0].Subresource = input_frame->subresource_index;
input_args.Transform.SourceRectangle.right = s->input_width;
input_args.Transform.SourceRectangle.bottom = s->input_height;
input_args.Transform.DestinationRectangle.right = s->width;
@@ -594,7 +1456,9 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
D3D12_VIDEO_PROCESS_OUTPUT_STREAM_ARGUMENTS output_args = {0};
- output_args.OutputStream[0].pTexture2D = output_resource;
+ /* Same reasoning as input_args.InputStream[0].Subresource above. */
+ output_args.OutputStream[0].pTexture2D = output_resource;
+ output_args.OutputStream[0].Subresource = output_frame->subresource_index;
output_args.TargetRectangle.right = s->width;
output_args.TargetRectangle.bottom = s->height;
@@ -606,56 +1470,191 @@ static int scale_d3d12_filter_frame(AVFilterLink *inlink, AVFrame *in)
&input_args
);
- for (int i = 0; i < 2; i++) {
+ 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, 2, barriers);
+ ID3D12VideoProcessCommandList_ResourceBarrier(s->command_list, 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 fail;
+ 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, s->fence, 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 fail;
+ goto submit_fail;
}
- output_frame->sync_ctx.fence = s->fence;
- output_frame->sync_ctx.fence_value = s->fence_value;
- ID3D12Fence_AddRef(s->fence); ///< Increment reference count
+ 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, command_allocator, s->fence_value);
+ ret = scale_d3d12_discard_command_allocator(s, ctx, command_allocator, next_private_fence_value,
+ input_ref);
command_allocator = NULL;
if (ret < 0)
goto fail;
- s->fence_value++;
-
ret = av_frame_copy_props(out, in);
if (ret < 0) {
av_log(ctx, AV_LOG_ERROR, "Failed to copy frame properties\n");
goto fail;
}
+ int in_width = in->width;
+ int in_height = in->height;
+
out->width = s->width;
out->height = s->height;
out->format = AV_PIX_FMT_D3D12;
+ if (out->width != in_width || out->height != in_height)
+ av_reduce(&out->sample_aspect_ratio.num, &out->sample_aspect_ratio.den,
+ (int64_t)in->sample_aspect_ratio.num * out->height * in_width,
+ (int64_t)in->sample_aspect_ratio.den * out->width * in_height,
+ INT_MAX);
+
+ if (out->width != in_width || out->height != in_height) {
+ av_frame_side_data_remove_by_props(&out->side_data, &out->nb_side_data,
+ AV_SIDE_DATA_PROP_SIZE_DEPENDENT);
+ out->crop_left = out->crop_right = out->crop_top = out->crop_bottom = 0;
+ }
+
+ out->colorspace = s->output_av_colorspace;
+ out->color_range = s->output_av_color_range;
+ out->color_primaries = s->output_av_color_primaries;
+ out->color_trc = s->output_av_color_trc;
+
+ if (out->color_primaries != s->input_av_color_primaries || out->color_trc != s->input_av_color_trc)
+ av_frame_side_data_remove_by_props(&out->side_data, &out->nb_side_data,
+ AV_SIDE_DATA_PROP_COLOR_DEPENDENT);
+
+ if (!s->output_has_alpha)
+ out->alpha_mode = AVALPHA_MODE_UNSPECIFIED;
+
+ if (s->output_is_rgb) {
+ out->chroma_location = AVCHROMA_LOC_UNSPECIFIED;
+ } else {
+ out->chroma_location = d3d12_scale_dxgi_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)
- scale_d3d12_discard_command_allocator(s, command_allocator, s->fence_value);
+ ID3D12CommandAllocator_Release(command_allocator);
av_frame_free(&in);
av_frame_free(&out);
return ret;
@@ -670,8 +1669,13 @@ static int scale_d3d12_config_props(AVFilterLink *outlink)
FilterLink *outl = ff_filter_link(outlink);
int ret;
- release_d3d12_resources(s);
+ if (!release_d3d12_resources(s, ctx)) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Cannot (re)configure scale_d3d12: a prior configuration's "
+ "D3D12 resources could not be confirmed released\n");
+ return AVERROR_EXTERNAL;
+ }
av_buffer_unref(&s->hw_frames_ctx_out);
av_buffer_unref(&s->hw_device_ctx);
@@ -689,6 +1693,14 @@ static int scale_d3d12_config_props(AVFilterLink *outlink)
outlink->w = s->width;
outlink->h = s->height;
+ if (inlink->sample_aspect_ratio.num) {
+ AVRational q = av_div_q((AVRational){ inlink->w, inlink->h },
+ (AVRational){ outlink->w, outlink->h });
+ outlink->sample_aspect_ratio = av_mul_q(q, inlink->sample_aspect_ratio);
+ } else {
+ outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
+ }
+
if (!inl->hw_frames_ctx) {
av_log(ctx, AV_LOG_ERROR, "No hw_frames_ctx available on input link\n");
return AVERROR(EINVAL);
@@ -720,35 +1732,43 @@ static int scale_d3d12_config_props(AVFilterLink *outlink)
AVHWFramesContext *frames_ctx = (AVHWFramesContext *)s->hw_frames_ctx_out->data;
AVHWFramesContext *in_frames_ctx = (AVHWFramesContext *)inl->hw_frames_ctx->data;
- if (s->format == AV_PIX_FMT_NONE) {
+ enum AVPixelFormat resolved_format = s->format;
+ if (resolved_format == AV_PIX_FMT_NONE) {
/* If format is not specified, use the same format as input */
- frames_ctx->sw_format = in_frames_ctx->sw_format;
- s->format = in_frames_ctx->sw_format;
+ resolved_format = in_frames_ctx->sw_format;
av_log(ctx, AV_LOG_VERBOSE, "D3D12 scale output format not specified, using input format: %s\n",
- av_get_pix_fmt_name(s->format));
- } else {
- frames_ctx->sw_format = s->format;
+ av_get_pix_fmt_name(resolved_format));
}
+ frames_ctx->sw_format = resolved_format;
/* Set output format based on sw_format */
- switch (frames_ctx->sw_format) {
- case AV_PIX_FMT_NV12:
- s->output_format = DXGI_FORMAT_NV12;
- break;
- case AV_PIX_FMT_P010:
- s->output_format = DXGI_FORMAT_P010;
- break;
- default:
- 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);
- return AVERROR(EINVAL);
+ const D3D12ScaleFormatMap *out_map = d3d12_scale_map_from_pixfmt(frames_ctx->sw_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);
+
+ 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);
+ 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 ||
+ (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 = 10;
+ frames_ctx->initial_pool_size = 4;
if (ctx->extra_hw_frames > 0)
frames_ctx->initial_pool_size += ctx->extra_hw_frames;
@@ -775,19 +1795,94 @@ static int scale_d3d12_config_props(AVFilterLink *outlink)
av_log(ctx, AV_LOG_VERBOSE, "D3D12 scale config: %dx%d -> %dx%d\n",
inlink->w, inlink->h, outlink->w, outlink->h);
+
return 0;
}
static av_cold void scale_d3d12_uninit(AVFilterContext *ctx) {
ScaleD3D12Context *s = ctx->priv;
- release_d3d12_resources(s);
-
- av_buffer_unref(&s->hw_frames_ctx_out);
- av_buffer_unref(&s->hw_device_ctx);
+ if (release_d3d12_resources(s, ctx)) {
+ av_buffer_unref(&s->hw_frames_ctx_out);
+ av_buffer_unref(&s->hw_device_ctx);
+ }
av_freep(&s->w_expr);
av_freep(&s->h_expr);
+
+ av_freep(&s->color_matrix_string);
+ av_freep(&s->color_transfer_string);
+ av_freep(&s->color_primaries_string);
+}
+
+static enum AVColorSpace d3d12_scale_matrix_from_primaries(enum AVColorPrimaries primaries)
+{
+ if (primaries == AVCOL_PRI_BT2020)
+ return AVCOL_SPC_BT2020_NCL;
+ if (primaries == AVCOL_PRI_BT470BG || primaries == AVCOL_PRI_SMPTE170M)
+ return AVCOL_SPC_SMPTE170M;
+ return AVCOL_SPC_BT709;
+}
+
+static int scale_d3d12_query_formats(const AVFilterContext *avctx,
+ AVFilterFormatsConfig **cfg_in,
+ AVFilterFormatsConfig **cfg_out)
+{
+ static const enum AVPixelFormat pix_fmts[] = {
+ AV_PIX_FMT_D3D12, AV_PIX_FMT_NONE,
+ };
+ static const int out_chroma_locs_any[] = {
+ AVCHROMA_LOC_UNSPECIFIED, AVCHROMA_LOC_LEFT, AVCHROMA_LOC_TOPLEFT, -1,
+ };
+ static const int out_chroma_locs_left[] = {
+ AVCHROMA_LOC_LEFT, -1,
+ };
+ const ScaleD3D12Context *s = avctx->priv;
+ AVFilterFormats *color_spaces, *color_ranges;
+ enum AVColorSpace out_matrix;
+ int out_matrix_known;
+ int ret;
+
+ ret = ff_set_pixel_formats_from_list2(avctx, cfg_in, cfg_out, pix_fmts);
+ if (ret < 0)
+ return ret;
+
+ out_matrix_known = s->color_matrix != AVCOL_SPC_UNSPECIFIED ||
+ s->color_primaries != AVCOL_PRI_UNSPECIFIED;
+ if (s->color_matrix != AVCOL_SPC_UNSPECIFIED)
+ out_matrix = s->color_matrix == AVCOL_SPC_BT2020_CL ? AVCOL_SPC_BT2020_NCL : s->color_matrix;
+ else if (s->color_primaries != AVCOL_PRI_UNSPECIFIED)
+ out_matrix = d3d12_scale_matrix_from_primaries(s->color_primaries);
+ else
+ out_matrix = AVCOL_SPC_UNSPECIFIED;
+
+ color_spaces = ff_all_color_spaces();
+ if ((ret = ff_formats_ref(color_spaces, &cfg_in[0]->color_spaces)) < 0)
+ return ret;
+ if (out_matrix_known)
+ color_spaces = ff_make_formats_list_singleton(out_matrix);
+ if ((ret = ff_formats_ref(color_spaces, &cfg_out[0]->color_spaces)) < 0)
+ return ret;
+
+ color_ranges = ff_all_color_ranges();
+ if ((ret = ff_formats_ref(color_ranges, &cfg_in[0]->color_ranges)) < 0)
+ return ret;
+ if (s->color_range != AVCOL_RANGE_UNSPECIFIED)
+ color_ranges = ff_make_formats_list_singleton(s->color_range);
+ if ((ret = ff_formats_ref(color_ranges, &cfg_out[0]->color_ranges)) < 0)
+ return ret;
+
+ if ((ret = ff_formats_ref(ff_all_chroma_locations(), &cfg_in[0]->chroma_locations)) < 0)
+ return ret;
+ if (out_matrix_known && out_matrix != AVCOL_SPC_BT2020_NCL) {
+ if ((ret = ff_formats_ref(ff_make_format_list(out_chroma_locs_left), &cfg_out[0]->chroma_locations)) < 0)
+ return ret;
+ } else {
+ if ((ret = ff_formats_ref(ff_make_format_list(out_chroma_locs_any), &cfg_out[0]->chroma_locations)) < 0)
+ return ret;
+ }
+
+ return 0;
}
static const AVFilterPad scale_d3d12_inputs[] = {
@@ -813,6 +1908,24 @@ static const AVOption scale_d3d12_options[] = {
{ "w", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, {.str = "iw"}, .flags = FLAGS },
{ "h", "Output video height", OFFSET(h_expr), AV_OPT_TYPE_STRING, {.str = "ih"}, .flags = FLAGS },
{ "format", "Output video pixel format", OFFSET(format), AV_OPT_TYPE_PIXEL_FMT, { .i64 = AV_PIX_FMT_NONE }, INT_MIN, INT_MAX, .flags=FLAGS },
+
+ /* These color properties match the identically named ones in vf_scale_vaapi. */
+ { "out_color_matrix", "Output color matrix coefficient set",
+ OFFSET(color_matrix_string), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = FLAGS },
+ { "out_range", "Output color range",
+ OFFSET(color_range), AV_OPT_TYPE_INT, { .i64 = AVCOL_RANGE_UNSPECIFIED },
+ AVCOL_RANGE_UNSPECIFIED, AVCOL_RANGE_JPEG, FLAGS, .unit = "range" },
+ { "full", "Full range", 0, AV_OPT_TYPE_CONST, { .i64 = AVCOL_RANGE_JPEG }, 0, 0, FLAGS, .unit = "range" },
+ { "limited", "Limited range", 0, AV_OPT_TYPE_CONST, { .i64 = AVCOL_RANGE_MPEG }, 0, 0, FLAGS, .unit = "range" },
+ { "jpeg", "Full range", 0, AV_OPT_TYPE_CONST, { .i64 = AVCOL_RANGE_JPEG }, 0, 0, FLAGS, .unit = "range" },
+ { "mpeg", "Limited range", 0, AV_OPT_TYPE_CONST, { .i64 = AVCOL_RANGE_MPEG }, 0, 0, FLAGS, .unit = "range" },
+ { "tv", "Limited range", 0, AV_OPT_TYPE_CONST, { .i64 = AVCOL_RANGE_MPEG }, 0, 0, FLAGS, .unit = "range" },
+ { "pc", "Full range", 0, AV_OPT_TYPE_CONST, { .i64 = AVCOL_RANGE_JPEG }, 0, 0, FLAGS, .unit = "range" },
+ { "out_color_primaries", "Output color primaries",
+ OFFSET(color_primaries_string), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = FLAGS },
+ { "out_color_transfer", "Output color transfer characteristics",
+ OFFSET(color_transfer_string), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = FLAGS },
+
{ NULL }
};
@@ -827,7 +1940,7 @@ const FFFilter ff_vf_scale_d3d12 = {
.uninit = scale_d3d12_uninit,
FILTER_INPUTS(scale_d3d12_inputs),
FILTER_OUTPUTS(scale_d3d12_outputs),
- FILTER_SINGLE_PIXFMT(AV_PIX_FMT_D3D12),
+ FILTER_QUERY_FUNC2(scale_d3d12_query_formats),
.p.flags = AVFILTER_FLAG_HWDEVICE,
.flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
};