PR #25060 opened by Kilian (lusoris)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25060
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25060.patch

This backports d0d7d81317 to release/9.0. Without it, a Vulkan-decoded HEVC 
stream re-encoded with hevc_vulkan and `-force_key_frames 2` stops at frame 61 
and never finishes: in 25 of 60 runs on the release/9.0 tip the process made no 
progress until it was killed. With the backport, all 60 runs encode every frame.

The cherry-pick (`-x`) applies without conflicts. It adds one line: `#include 
"thread.h"` in libavutil/vulkan.c, because master gets `ff_thread_once()` 
through vulkan.h and release/9.0 does not. The commit message notes the 
addition.

## Reproduction on Linux (before / after)

NVIDIA GeForce RTX 4090, driver 615.78.08. Built release/9.0 `7e93014c5b` with 
and without the backport, minimal configure (`--disable-autodetect 
--enable-vulkan`, hevc decoder, hevc_vulkan hwaccel and encoder, matroska, file 
protocol):

```sh
timeout -k 5 30 ffmpeg -nostdin -y -init_hw_device vulkan=vk:0 
-filter_hw_device vk \
    -hwaccel vulkan -hwaccel_device vk -hwaccel_output_format vulkan -i in.mkv \
    -force_key_frames 2 -c:v hevc_vulkan -qp 28 out.mkv
```

`in.mkv` is 120 frames of 1280x720 yuv420p HEVC at 30 fps whose only keyframe 
is frame 0 (2 s of testsrc2 followed by 2 s of smptehdbars, encoded with 
`hevc_nvenc -g 250 -bf 0 -no-scenecut 1`). The forced keyframe falls on frame 
60.

| 60 runs each | all 120 frames | killed by the timeout | first hang |
| --- | --- | --- | --- |
| release/9.0 `7e93014c5b` | 35 | 25 | run 1, at frame 61 |
| with backport | 60 | 0 | none |

Every progress line of a hung run reads `frame=   61`, up to the kill at 30 s.

## How the commit was found

Bisect between the release/9.0 branch point (`47e9d68e20`) and master 
(`ebfc9497f7`), limited to the Vulkan, hw_base_encode and fftools paths, 60 
runs per step. A step counted as good only when all 60 runs finished:

- `8884301271` (parent of d0d7d81317): hangs in run 1, at frame 61.
- `d0d7d81317`: 60 of 60 runs finish.

On master, reverting d0d7d81317 at `ebfc9497f7` still gives 60 of 60 clean 
runs, so a later master change also hides the hang there. On release/9.0, this 
commit alone fixes it, as the table shows.

## Checks on the backport

- `make fate-source` passes.
- Both minimal builds report the same 10 warnings; the backport adds none.
- `libavcodec/ffv1enc_vulkan.o` and `libavutil/vulkan.o` compile with no 
warnings in a configure that adds `--enable-encoder=ffv1_vulkan`.



>From 669f5ab083c7732c10f1ce9e4f1915960dd88457 Mon Sep 17 00:00:00 2001
From: Lynne <[email protected]>
Date: Sat, 18 Jul 2026 15:04:56 +0800
Subject: [PATCH] vulkan: de-phase queue rotation and pin video pools to a
 single queue

All pools rotated through a family's queues in lockstep from index 0,
colliding on the same queue at the same time. Give each pool a phase
from a per-family, per-library counter: video pools are pinned to the
queue it selects, as sessions order their execution anyway, while all
other pools rotate starting from it. On e.g. Intel, a transcode now
runs decode and encode on separate engines of the shared video family.

The counters are seeded once with a random value, so that separate
processes, and separate copies of this code within one process,
de-phase statistically on drivers with a fixed queue-to-engine mapping.

Also assigns ffv1enc's per-context index once, at init.

(cherry picked from commit d0d7d813178fec964716aa2c48f5896ce5c26284)
[lusoris: include thread.h for ff_thread_once(); master gets it through 
vulkan.h]
Signed-off-by: lusoris <[email protected]>
---
 libavcodec/ffv1enc_vulkan.c |  5 +++--
 libavutil/vulkan.c          | 25 ++++++++++++++++++++++++-
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/libavcodec/ffv1enc_vulkan.c b/libavcodec/ffv1enc_vulkan.c
index 2259e841d4..041e091e59 100644
--- a/libavcodec/ffv1enc_vulkan.c
+++ b/libavcodec/ffv1enc_vulkan.c
@@ -417,7 +417,6 @@ static int vulkan_encode_ffv1_submit_frame(AVCodecContext 
*avctx,
 
     /* Start recording */
     ff_vk_exec_start(&fv->s, exec);
-    fd->idx = exec->idx;
 
     /* For float pixel formats we want the raw bit pattern, not a value
      * already passed through fp16/fp32 conversion (which can flush
@@ -1539,8 +1538,10 @@ static av_cold int 
vulkan_encode_ffv1_init(AVCodecContext *avctx)
     fv->exec_ctx_info = av_calloc(fv->async_depth, sizeof(*fv->exec_ctx_info));
     if (!fv->exec_ctx_info)
         return AVERROR(ENOMEM);
-    for (int i = 0; i < fv->async_depth; i++)
+    for (int i = 0; i < fv->async_depth; i++) {
         fv->exec_pool.contexts[i].opaque = &fv->exec_ctx_info[i];
+        fv->exec_ctx_info[i].idx = fv->exec_pool.contexts[i].idx;
+    }
 
     fv->buf_regions = av_malloc_array(f->max_slice_count, 
sizeof(*fv->buf_regions));
     if (!fv->buf_regions)
diff --git a/libavutil/vulkan.c b/libavutil/vulkan.c
index 3b2cc35bf9..2d80e956c7 100644
--- a/libavutil/vulkan.c
+++ b/libavutil/vulkan.c
@@ -21,6 +21,8 @@
 #include "config.h"
 #include "avassert.h"
 #include "mem.h"
+#include "random_seed.h"
+#include "thread.h"
 
 #include "vulkan.h"
 #include "libavutil/vulkan_loader.h"
@@ -354,6 +356,18 @@ void ff_vk_exec_pool_free(FFVulkanContext *s, FFVkExecPool 
*pool)
     pool->pool_size = 0;
 }
 
+/* Per-family, per-library queue selection phases; each library links its own
+ * copy, and pools which benefit from spreading are created by the same one */
+static atomic_uint exec_pool_phase[FF_ARRAY_ELEMS(((AVVulkanDeviceContext 
*)NULL)->qf)];
+static AVOnce exec_pool_phase_seeded = AV_ONCE_INIT;
+
+static void exec_pool_phase_seed(void)
+{
+    uint32_t seed = av_get_random_seed();
+    for (int i = 0; i < FF_ARRAY_ELEMS(exec_pool_phase); i++)
+        atomic_store_explicit(&exec_pool_phase[i], seed, memory_order_relaxed);
+}
+
 int ff_vk_exec_pool_init(FFVulkanContext *s, AVVulkanDeviceQueueFamily *qf,
                          FFVkExecPool *pool, int nb_contexts,
                          int nb_queries, VkQueryType query_type, int 
query_64bit,
@@ -488,6 +502,15 @@ int ff_vk_exec_pool_init(FFVulkanContext *s, 
AVVulkanDeviceQueueFamily *qf,
     }
 #endif
 
+    /* Video pools are pinned to the phase-picked queue (sessions order their
+     * execution anyway); all other pools rotate starting from it, so pools
+     * advancing in lockstep do not collide. */
+    av_assert1(qf->idx < FF_ARRAY_ELEMS(exec_pool_phase));
+    ff_thread_once(&exec_pool_phase_seeded, exec_pool_phase_seed);
+    uint32_t phase = atomic_fetch_add(&exec_pool_phase[qf->idx], 1);
+    int pin_queue = qf->flags & (VK_QUEUE_VIDEO_DECODE_BIT_KHR |
+                                 VK_QUEUE_VIDEO_ENCODE_BIT_KHR);
+
     /* Init contexts */
     for (int i = 0; i < pool->pool_size; i++) {
         FFVkExecContext *e = &pool->contexts[i];
@@ -516,7 +539,7 @@ int ff_vk_exec_pool_init(FFVulkanContext *s, 
AVVulkanDeviceQueueFamily *qf,
         e->buf = pool->cmd_bufs[i];
 
         /* Queue index distribution */
-        e->qi = i % qf->num;
+        e->qi = pin_queue ? phase % qf->num : (i + phase) % qf->num;
         e->qf = qf->idx;
         VkDeviceQueueInfo2 qinfo = {
             .sType            = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2,
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to