From 2cf3f4d64de0efa5ccb4021f7245e93b041dbd9e Mon Sep 17 00:00:00 2001 From: Philip Langdale Date: Wed, 15 Jul 2026 12:56:26 -0700 Subject: [PATCH] avutil/hwcontext_cuda: remove format allowlist, accept any pixel format Remove the static supported_formats[] allowlist from hwcontext_cuda. cuda_frames_get_constraints() now iterates all registered pixel formats and includes every non-hwaccel, non-palette format. cuda_frames_init() validates with av_pix_fmt_desc_get() instead of checking against the allowlist. Palette formats have a special-cased use of plane[1] for the palette itself and it's not worth the hassle of supporting in an otherwise clean generic copy loop. Also add a roundtrip test that uploads a deterministic byte pattern to CUDA, downloads it back, and verifies the data match. This tests all non-hwaccel pixel formats. The test is gated on CONFIG_CUDA. --- libavutil/Makefile | 1 + libavutil/hwcontext_cuda.c | 87 ++++++------- libavutil/tests/.gitignore | 1 + libavutil/tests/hwcontext_cuda.c | 215 +++++++++++++++++++++++++++++++ tests/fate/hw.mak | 5 + 5 files changed, 258 insertions(+), 51 deletions(-) create mode 100644 libavutil/tests/hwcontext_cuda.c diff --git a/libavutil/Makefile b/libavutil/Makefile index c3cadf5c2f..9cb3108b38 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -325,6 +325,7 @@ TESTPROGS = adler32 \ xtea \ tea \ +TESTPROGS-$(CONFIG_CUDA) += hwcontext_cuda TESTPROGS-$(HAVE_THREADS) += cpu_init TESTPROGS-$(HAVE_LZO1X_999_COMPRESS) += lzo diff --git a/libavutil/hwcontext_cuda.c b/libavutil/hwcontext_cuda.c index a057c36b20..b38573b04b 100644 --- a/libavutil/hwcontext_cuda.c +++ b/libavutil/hwcontext_cuda.c @@ -44,37 +44,7 @@ typedef struct CUDADeviceContext { AVCUDADeviceContextInternal internal; } CUDADeviceContext; -static const enum AVPixelFormat supported_formats[] = { - AV_PIX_FMT_NV12, - AV_PIX_FMT_NV16, - AV_PIX_FMT_YUV420P, - AV_PIX_FMT_YUVA420P, - AV_PIX_FMT_YUV444P, - AV_PIX_FMT_NV24, - AV_PIX_FMT_P010, - AV_PIX_FMT_P012, - AV_PIX_FMT_P016, - AV_PIX_FMT_P210, - AV_PIX_FMT_P212, - AV_PIX_FMT_P216, - AV_PIX_FMT_P410, - AV_PIX_FMT_P412, - AV_PIX_FMT_P416, - AV_PIX_FMT_YUV422P, - AV_PIX_FMT_YUV420P10, - AV_PIX_FMT_YUV422P10, - AV_PIX_FMT_YUV444P10, - AV_PIX_FMT_YUV444P10MSB, - AV_PIX_FMT_YUV444P12MSB, - AV_PIX_FMT_YUV444P16, - AV_PIX_FMT_0RGB32, - AV_PIX_FMT_0BGR32, - AV_PIX_FMT_RGB32, - AV_PIX_FMT_BGR32, -#if CONFIG_VULKAN - AV_PIX_FMT_VULKAN, -#endif -}; + #define CHECK_CU(x) FF_CUDA_CHECK_DL(device_ctx, cu, x) @@ -86,20 +56,7 @@ static int cuda_frames_get_constraints(AVHWDeviceContext *ctx, { const AVCUDAHWConfig *config = hwconfig; enum AVPixelFormat req_fmt = config ? config->hw_format : AV_PIX_FMT_NONE; - int i, nb_sw_formats = 0; - - constraints->valid_sw_formats = av_malloc_array(FF_ARRAY_ELEMS(supported_formats) + 1, - sizeof(*constraints->valid_sw_formats)); - if (!constraints->valid_sw_formats) - return AVERROR(ENOMEM); - - for (i = 0; i < FF_ARRAY_ELEMS(supported_formats); i++) { - if (req_fmt == AV_PIX_FMT_CUARRAY && - !cuda_array_format_for_pix_fmt(supported_formats[i])) - continue; - constraints->valid_sw_formats[nb_sw_formats++] = supported_formats[i]; - } - constraints->valid_sw_formats[nb_sw_formats] = AV_PIX_FMT_NONE; + int n = 0; if (req_fmt == AV_PIX_FMT_CUDA || req_fmt == AV_PIX_FMT_CUARRAY) { constraints->valid_hw_formats = av_malloc_array(2, sizeof(*constraints->valid_hw_formats)); @@ -122,6 +79,31 @@ static int cuda_frames_get_constraints(AVHWDeviceContext *ctx, #endif } + constraints->valid_sw_formats = av_malloc_array(AV_PIX_FMT_NB + 1, + sizeof(*constraints->valid_sw_formats)); + if (!constraints->valid_sw_formats) + return AVERROR(ENOMEM); + + n = 0; + for (int i = 0; i < AV_PIX_FMT_NB; i++) { + if (req_fmt == AV_PIX_FMT_CUARRAY) { + if (cuda_array_format_for_pix_fmt(i)) + constraints->valid_sw_formats[n++] = i; + } else { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(i); + /* Palette formats carry their palette in a separate zero-linesize + * data[1] plane that the transfer path does not handle, so exclude + * them along with the hwaccel formats. */ + if (desc && !(desc->flags & (AV_PIX_FMT_FLAG_HWACCEL | AV_PIX_FMT_FLAG_PAL))) + constraints->valid_sw_formats[n++] = i; + } + } +#if CONFIG_VULKAN + if (req_fmt != AV_PIX_FMT_CUARRAY) + constraints->valid_sw_formats[n++] = AV_PIX_FMT_VULKAN; +#endif + constraints->valid_sw_formats[n] = AV_PIX_FMT_NONE; + return 0; } @@ -290,15 +272,18 @@ static int cuda_frames_init(AVHWFramesContext *ctx) AVCUDADeviceContext *hwctx = device_ctx->hwctx; CUDAFramesContext *priv = ctx->hwctx; CudaFunctions *cu = hwctx->internal->cuda_dl; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(ctx->sw_format); int err, i; - for (i = 0; i < FF_ARRAY_ELEMS(supported_formats); i++) { - if (ctx->sw_format == supported_formats[i]) - break; + if (!desc) { + av_log(ctx, AV_LOG_ERROR, "Invalid pixel format\n"); + return AVERROR(EINVAL); } - if (i == FF_ARRAY_ELEMS(supported_formats)) { - av_log(ctx, AV_LOG_ERROR, "Pixel format '%s' is not supported\n", - av_get_pix_fmt_name(ctx->sw_format)); + + /* Palette formats keep their palette in a separate zero-linesize plane + * that the transfer path does not copy, so they are not supported. */ + if (desc->flags & AV_PIX_FMT_FLAG_PAL) { + av_log(ctx, AV_LOG_ERROR, "Palette formats are not supported\n"); return AVERROR(ENOSYS); } diff --git a/libavutil/tests/.gitignore b/libavutil/tests/.gitignore index 04043494ec..0f43921ed6 100644 --- a/libavutil/tests/.gitignore +++ b/libavutil/tests/.gitignore @@ -31,6 +31,7 @@ /hash /hdr_dynamic_vivid_metadata /hmac +/hwcontext_cuda /hwdevice /imgutils /integer diff --git a/libavutil/tests/hwcontext_cuda.c b/libavutil/tests/hwcontext_cuda.c new file mode 100644 index 0000000000..e4dec7ec05 --- /dev/null +++ b/libavutil/tests/hwcontext_cuda.c @@ -0,0 +1,215 @@ +/* + * 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 + */ + +#include +#include + +#include "libavutil/hwcontext.h" +#include "libavutil/imgutils.h" +#include "libavutil/log.h" +#include "libavutil/mem.h" +#include "libavutil/pixdesc.h" +#include "libavutil/pixfmt.h" + +static int test_format(AVBufferRef *device_ref, enum AVPixelFormat fmt) +{ + AVBufferRef *frames_ref = NULL; + AVHWFramesContext *hwfc; + AVFrame *sw_frame = NULL, *hw_frame = NULL, *download = NULL; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt); + int ret; + + frames_ref = av_hwframe_ctx_alloc(device_ref); + if (!frames_ref) { + ret = AVERROR(ENOMEM); + goto fail; + } + + hwfc = (AVHWFramesContext *)frames_ref->data; + hwfc->format = AV_PIX_FMT_CUDA; + hwfc->sw_format = fmt; + hwfc->width = 64; + hwfc->height = 64; + + ret = av_hwframe_ctx_init(frames_ref); + if (ret < 0) + goto fail; + + sw_frame = av_frame_alloc(); + hw_frame = av_frame_alloc(); + download = av_frame_alloc(); + if (!sw_frame || !hw_frame || !download) { + ret = AVERROR(ENOMEM); + goto fail; + } + + sw_frame->format = fmt; + sw_frame->width = 64; + sw_frame->height = 64; + ret = av_frame_get_buffer(sw_frame, 0); + if (ret < 0) + goto fail; + + { + int linesizes[4]; + av_image_fill_linesizes(linesizes, fmt, 64); + + for (int i = 0; i < FF_ARRAY_ELEMS(sw_frame->data) && sw_frame->data[i]; i++) { + int shift = (i == 1 || i == 2) && desc ? desc->log2_chroma_h : 0; + int h = AV_CEIL_RSHIFT(64, shift); + + for (int y = 0; y < h; y++) + for (int x = 0; x < linesizes[i]; x++) + sw_frame->data[i][y * sw_frame->linesize[i] + x] = + (uint8_t)(x + y * 3 + i * 17); + } + } + + hw_frame->hw_frames_ctx = av_buffer_ref(frames_ref); + if (!hw_frame->hw_frames_ctx) { + ret = AVERROR(ENOMEM); + goto fail; + } + + ret = av_hwframe_get_buffer(frames_ref, hw_frame, 0); + if (ret < 0) + goto fail; + + ret = av_hwframe_transfer_data(hw_frame, sw_frame, 0); + if (ret < 0) + goto fail; + + download->format = fmt; + download->width = 64; + download->height = 64; + ret = av_frame_get_buffer(download, 0); + if (ret < 0) + goto fail; + + ret = av_hwframe_transfer_data(download, hw_frame, 0); + if (ret < 0) + goto fail; + + { + int linesizes[4]; + av_image_fill_linesizes(linesizes, fmt, 64); + + for (int i = 0; i < FF_ARRAY_ELEMS(download->data) && download->data[i]; i++) { + int shift = (i == 1 || i == 2) && desc ? desc->log2_chroma_h : 0; + int h = AV_CEIL_RSHIFT(64, shift); + + for (int y = 0; y < h; y++) { + int off = y * FFMIN(sw_frame->linesize[i], download->linesize[i]); + if (memcmp(sw_frame->data[i] + off, + download->data[i] + off, + linesizes[i])) { + printf("fail: %-16s plane %d row %d mismatch\n", + av_get_pix_fmt_name(fmt), i, y); + ret = AVERROR(EINVAL); + goto fail; + } + } + } + } + +fail: + av_frame_free(&sw_frame); + av_frame_free(&hw_frame); + av_frame_free(&download); + av_buffer_unref(&frames_ref); + return ret; +} + +/* Verify that an unsupported sw_format is refused at init time. Returns 0 when + * the format is correctly rejected, and an error when it is wrongly accepted. */ +static int test_rejected(AVBufferRef *device_ref, enum AVPixelFormat fmt) +{ + AVBufferRef *frames_ref = av_hwframe_ctx_alloc(device_ref); + AVHWFramesContext *hwfc; + int ret; + + if (!frames_ref) + return AVERROR(ENOMEM); + + hwfc = (AVHWFramesContext *)frames_ref->data; + hwfc->format = AV_PIX_FMT_CUDA; + hwfc->sw_format = fmt; + hwfc->width = 64; + hwfc->height = 64; + + /* Init is expected to fail; silence the resulting error log. */ + av_log_set_level(AV_LOG_QUIET); + ret = av_hwframe_ctx_init(frames_ref); + av_log_set_level(AV_LOG_INFO); + + av_buffer_unref(&frames_ref); + + return ret >= 0 ? AVERROR(EINVAL) : 0; +} + +int main(void) +{ + AVBufferRef *device_ref = NULL; + enum AVPixelFormat fmt; + int ret, failures = 0, total = 0; + + ret = av_hwdevice_ctx_create(&device_ref, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0); + if (ret < 0) { + /* CONFIG_CUDA only requires the ffnvcodec headers and a dynamic + * loader; a FATE host that compiled CUDA support need not have an + * NVIDIA GPU or usable driver. Skip cleanly in that case so that + * make fate does not fail for an unavailable test environment. A real + * transfer mismatch below still returns a nonzero exit status. */ + printf("No CUDA device available, skipping.\n"); + return 0; + } + + for (fmt = 0; fmt < AV_PIX_FMT_NB; fmt++) { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt); + if (!desc || (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) + continue; + + total++; + /* Palette formats must be refused rather than silently dropping the + * palette across a transfer; every other format must round-trip. */ + if (desc->flags & AV_PIX_FMT_FLAG_PAL) { + if (test_rejected(device_ref, fmt) < 0) { + printf("fail: %-16s wrongly accepted\n", + av_get_pix_fmt_name(fmt)); + failures++; + } + } else { + ret = test_format(device_ref, fmt); + if (ret < 0) { + printf("fail: %-16s %s\n", + av_get_pix_fmt_name(fmt), + av_err2str(ret)); + failures++; + } + } + } + + av_buffer_unref(&device_ref); + + if (failures) + printf("%d / %d tests failed.\n", failures, total); + else + printf("%d tests passed.\n", total); + + return !!failures; +} diff --git a/tests/fate/hw.mak b/tests/fate/hw.mak index 0f126892fd..ea8bdf4dc4 100644 --- a/tests/fate/hw.mak +++ b/tests/fate/hw.mak @@ -3,4 +3,9 @@ fate-hwdevice: libavutil/tests/hwdevice$(EXESUF) fate-hwdevice: CMD = run libavutil/tests/hwdevice$(EXESUF) fate-hwdevice: CMP = null +FATE_HW-$(CONFIG_CUDA) += fate-hwcontext-cuda +fate-hwcontext-cuda: libavutil/tests/hwcontext_cuda$(EXESUF) +fate-hwcontext-cuda: CMD = run libavutil/tests/hwcontext_cuda$(EXESUF) +fate-hwcontext-cuda: CMP = null + FATE_HW-$(CONFIG_AVUTIL) += $(FATE_HWCONTEXT)