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.
This commit is contained in:
Philip Langdale
2026-07-15 12:56:26 -07:00
committed by philipl
parent c450bf833c
commit 2cf3f4d64d
5 changed files with 258 additions and 51 deletions

View File

@@ -325,6 +325,7 @@ TESTPROGS = adler32 \
xtea \
tea \
TESTPROGS-$(CONFIG_CUDA) += hwcontext_cuda
TESTPROGS-$(HAVE_THREADS) += cpu_init
TESTPROGS-$(HAVE_LZO1X_999_COMPRESS) += lzo

View File

@@ -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);
}

View File

@@ -31,6 +31,7 @@
/hash
/hdr_dynamic_vivid_metadata
/hmac
/hwcontext_cuda
/hwdevice
/imgutils
/integer

View File

@@ -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 <stdio.h>
#include <string.h>
#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;
}

View File

@@ -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)