Commit 4d218e16d4 for ffmpeg

commit 4d218e16d4254206825ac958911b3f616441004d
Author: Michael Niedermayer <michael@niedermayer.cc>
Date:   Sun Oct 4 03:34:55 2026 +0200

    avutil/hwcontext_cuda: allocate transfer buffers from page-locked memory

    The device transfers directly to and from page-locked host memory,
    while pageable memory is copied through a buffer of the driver.
    Pageable memory remains in use when page-locked memory cannot be
    allocated and with ffnvcodec headers that lack cuMemHostAlloc().

    As a page-locked source is read by the device after the upload
    returns, uploads now wait for the transfer to complete, like downloads.

    ffmpeg -hwaccel cuda -i hevc.mp4 -f null - on a RTX 5090, mean of 10
    runs: 1080p 1773 -> 1855 fps, 2160p 497 -> 533 fps, 4320p 112 -> 124 fps.

    Assisted-by: Claude

diff --git a/configure b/configure
index 192a82f5b3..1221926d46 100755
--- a/configure
+++ b/configure
@@ -2604,6 +2604,7 @@ TOOLCHAIN_FEATURES="
 "

 TYPES_LIST="
+    CudaFunctions_cuMemHostAlloc
     DPI_AWARENESS_CONTEXT
     IDXGIOutput5
     __x_ABI_CWindows_CGraphics_CCapture_CIGraphicsCaptureSession5
@@ -7121,6 +7122,7 @@ fi

 if enabled ffnvcodec; then
     check_cpp_condition ffnvcodec_cuarray ffnvcodec/dynlink_loader.h "defined(CUDA_ARRAY3D_VIDEO_ENCODE_DECODE)"
+    check_struct ffnvcodec/dynlink_loader.h CudaFunctions cuMemHostAlloc
 fi

 check_cpp_condition winrt windows.h "!WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)"
diff --git a/libavutil/hwcontext_cuda.c b/libavutil/hwcontext_cuda.c
index 776c321652..d8012e8235 100644
--- a/libavutil/hwcontext_cuda.c
+++ b/libavutil/hwcontext_cuda.c
@@ -196,6 +196,44 @@ done:
     return ret;
 }

+#if HAVE_CUDAFUNCTIONS_CUMEMHOSTALLOC
+static void cuda_transfer_buffer_free(void *opaque, uint8_t *data)
+{
+    AVHWDeviceContext *device_ctx = opaque;
+    AVCUDADeviceContext    *hwctx = device_ctx->hwctx;
+    CudaFunctions             *cu = hwctx->internal->cuda_dl;
+    CUcontext dummy;
+
+    CHECK_CU(cu->cuCtxPushCurrent(hwctx->cuda_ctx));
+    CHECK_CU(cu->cuMemFreeHost(data));
+    CHECK_CU(cu->cuCtxPopCurrent(&dummy));
+}
+
+/* The device transfers directly to and from page-locked memory, other memory
+ * goes through a buffer of the driver. */
+static AVBufferRef *cuda_transfer_buffer_alloc(void *device_ref, size_t size)
+{
+    AVHWDeviceContext *device_ctx = (AVHWDeviceContext *)((AVBufferRef *)device_ref)->data;
+    AVCUDADeviceContext    *hwctx = device_ctx->hwctx;
+    CudaFunctions             *cu = hwctx->internal->cuda_dl;
+    AVBufferRef *ret = NULL;
+    CUcontext dummy;
+    void *host;
+
+    if (CHECK_CU(cu->cuCtxPushCurrent(hwctx->cuda_ctx)) < 0)
+        return av_buffer_alloc(size);
+
+    if (CHECK_CU(cu->cuMemHostAlloc(&host, size, 0)) >= 0) {
+        ret = av_buffer_create(host, size, cuda_transfer_buffer_free, device_ctx, 0);
+        if (!ret)
+            CHECK_CU(cu->cuMemFreeHost(host));
+    }
+
+    CHECK_CU(cu->cuCtxPopCurrent(&dummy));
+    return ret ? ret : av_buffer_alloc(size);
+}
+#endif
+
 static void cuda_frames_uninit(AVHWFramesContext *ctx)
 {
     AVHWDeviceContext *device_ctx = ctx->device_ctx;
@@ -645,7 +683,8 @@ static int cuda_transfer_data(AVHWFramesContext *ctx, AVFrame *dst,
             break;
     }

-    if (!dst->hw_frames_ctx) {
+    /* The caller may reuse host memory once the transfer returns. */
+    if (!src->hw_frames_ctx || !dst->hw_frames_ctx) {
         ret = CHECK_CU(cu->cuStreamSynchronize(hwctx->stream));
         if (ret < 0)
             goto exit;
@@ -951,6 +990,9 @@ const HWContextType ff_hwcontext_type_cuda = {
     .frames_uninit        = cuda_frames_uninit,
     .frames_get_buffer    = cuda_get_buffer,
     .transfer_get_formats = cuda_transfer_get_formats,
+#if HAVE_CUDAFUNCTIONS_CUMEMHOSTALLOC
+    .transfer_buffer_alloc = cuda_transfer_buffer_alloc,
+#endif
     .transfer_data_to     = cuda_transfer_data,
     .transfer_data_from   = cuda_transfer_data,

diff --git a/libavutil/tests/hwcontext_cuda.c b/libavutil/tests/hwcontext_cuda.c
index 3d6e89a9b1..05d11ca8c7 100644
--- a/libavutil/tests/hwcontext_cuda.c
+++ b/libavutil/tests/hwcontext_cuda.c
@@ -16,15 +16,20 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */

+#include <inttypes.h>
+#include <stdatomic.h>
 #include <stdio.h>
 #include <string.h>

+#include "libavutil/adler32.h"
 #include "libavutil/hwcontext.h"
+#include "libavutil/hwcontext_cuda_internal.h"
 #include "libavutil/imgutils.h"
 #include "libavutil/log.h"
 #include "libavutil/mem.h"
 #include "libavutil/pixdesc.h"
 #include "libavutil/pixfmt.h"
+#include "libavutil/time.h"

 static int test_format(AVBufferRef *device_ref, enum AVPixelFormat fmt)
 {
@@ -129,6 +134,83 @@ fail:
     return ret;
 }

+static atomic_int source_overwritten;
+
+/* Hold the stream until the source was overwritten, at most 100 ms. */
+static void CUDAAPI hold_stream(CUstream stream, CUresult status, void *opaque)
+{
+    int64_t start = av_gettime_relative();
+    while (!atomic_load(&source_overwritten) && av_gettime_relative() - start < 100000);
+}
+
+/* Reuse the memory of an uploaded transfer buffer while the stream is still
+ * busy, the upload must not see the new contents. */
+static int test_source_reuse(AVBufferRef *device_ref)
+{
+    AVCUDADeviceContext *hwctx = ((AVHWDeviceContext *)device_ref->data)->hwctx;
+    CudaFunctions *cu = hwctx->internal->cuda_dl;
+    AVBufferRef *frames_ref = av_hwframe_ctx_alloc(device_ref);
+    AVFrame *sw_frame = av_frame_alloc(), *hw_frame = av_frame_alloc();
+    AVFrame *download = av_frame_alloc();
+    AVHWFramesContext *hwfc;
+    CUcontext dummy;
+    AVAdler checksum = 1;
+    int ret = AVERROR(ENOMEM);
+
+    if (!frames_ref || !sw_frame || !hw_frame || !download)
+        goto fail;
+
+    hwfc = (AVHWFramesContext *)frames_ref->data;
+    hwfc->format    = AV_PIX_FMT_CUDA;
+    hwfc->sw_format = AV_PIX_FMT_GRAY8;
+    hwfc->width     = 64;
+    hwfc->height    = 64;
+    if ((ret = av_hwframe_ctx_init(frames_ref)) < 0 ||
+        (ret = av_hwframe_get_buffer(frames_ref, hw_frame, 0)) < 0)
+        goto fail;
+
+    for (int value = 0x11; value <= 0x22; value += 0x11) {
+        av_frame_unref(sw_frame);
+        sw_frame->format = AV_PIX_FMT_GRAY8;
+        sw_frame->width  = 64;
+        sw_frame->height = 64;
+        if ((ret = av_hwframe_get_transfer_buffer(frames_ref, sw_frame)) < 0)
+            goto fail;
+        for (int y = 0; y < 64; y++)
+            memset(sw_frame->data[0] + y * sw_frame->linesize[0], value, 64);
+        if (value == 0x22) {
+            atomic_store(&source_overwritten, 1);
+            break;
+        }
+
+        ret = AVERROR_EXTERNAL;
+        if (cu->cuCtxPushCurrent(hwctx->cuda_ctx) != CUDA_SUCCESS)
+            goto fail;
+        if (cu->cuStreamAddCallback(hwctx->stream, hold_stream, NULL, 0) == CUDA_SUCCESS)
+            ret = 0;
+        cu->cuCtxPopCurrent(&dummy);
+        if (ret < 0)
+            goto fail;
+
+        if ((ret = av_hwframe_transfer_data(hw_frame, sw_frame, 0)) < 0)
+            goto fail;
+    }
+
+    if ((ret = av_hwframe_transfer_data(download, hw_frame, 0)) < 0)
+        goto fail;
+    for (int y = 0; y < 64; y++)
+        checksum = av_adler32_update(checksum,
+                                     download->data[0] + y * download->linesize[0], 64);
+    fprintf(stderr, "source reuse checksum: %08"PRIx32"\n", checksum);
+
+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)
@@ -169,7 +251,7 @@ int main(void)
          * 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");
+        fprintf(stderr, "No CUDA device available, skipping.\n");
         return 0;
     }

@@ -198,6 +280,13 @@ int main(void)
         }
     }

+    total++;
+    ret = test_source_reuse(device_ref);
+    if (ret < 0) {
+        printf("fail: source reuse %s\n", av_err2str(ret));
+        failures++;
+    }
+
     av_buffer_unref(&device_ref);

     if (failures)
diff --git a/tests/fate/hw.mak b/tests/fate/hw.mak
index ea8bdf4dc4..3f50ec73f4 100644
--- a/tests/fate/hw.mak
+++ b/tests/fate/hw.mak
@@ -6,6 +6,7 @@ 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-hwcontext-cuda: CMP = grep
+fate-hwcontext-cuda: REF = ^\(source reuse checksum: 178f1010\|No CUDA device available, skipping\.\)$$

 FATE_HW-$(CONFIG_AVUTIL) += $(FATE_HWCONTEXT)