mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2026-08-09 01:21:06 +00:00
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:
@@ -325,6 +325,7 @@ TESTPROGS = adler32 \
|
||||
xtea \
|
||||
tea \
|
||||
|
||||
TESTPROGS-$(CONFIG_CUDA) += hwcontext_cuda
|
||||
TESTPROGS-$(HAVE_THREADS) += cpu_init
|
||||
TESTPROGS-$(HAVE_LZO1X_999_COMPRESS) += lzo
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
1
libavutil/tests/.gitignore
vendored
1
libavutil/tests/.gitignore
vendored
@@ -31,6 +31,7 @@
|
||||
/hash
|
||||
/hdr_dynamic_vivid_metadata
|
||||
/hmac
|
||||
/hwcontext_cuda
|
||||
/hwdevice
|
||||
/imgutils
|
||||
/integer
|
||||
|
||||
215
libavutil/tests/hwcontext_cuda.c
Normal file
215
libavutil/tests/hwcontext_cuda.c
Normal 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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user