PR #25360 opened by Marcos Ashton (MarcosAsh)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25360
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25360.patch

Fixes hardware decoding of VP9 streams that change size between keyframes.

update_size() renegotiated the hwaccel on every frame size change, which throws 
away the decoder instance that holds the references. VP9 frames other than 
keyframes can change size and still predict from those references, so in 
vp90-2-05-resize.ivf every frame after the change came out wrong with Vulkan, 
and the validation layer reports the references as inactive slots. The decoder 
now remembers the size it negotiated the hwaccel for and keeps it on frames 
that fit. Keyframes still renegotiate, and so does a frame that grows past that 
size, which this doesn't fix.

With Vulkan the resize stream is now bit-exact with the software decoder, with 
and without frame threads, and the other 225 VP9 conformance streams give the 
same result as before. fate-vp9 passes. Master also segfaults on this stream 
with -hwaccel vulkan -threads 1, that's the crash #24953 fixes.

NVDEC gets most of the way. On master frames 3 to 9 of the resize stream are 
wrong. With this 6 to 9 are bit-exact, but the three 282x173 frames come out at 
352x288, because nvdec creates the decoder with the target size set to the 
coded size and scales to it. I think that's better as a separate nvdec fix. 
VDPAU recreates its decoder on every size change in ff_vdpau_common_reinit() 
anyway, so nothing changes there. I couldn't test VAAPI, D3D or VideoToolbox.

To reproduce, compare `ffmpeg -hwaccel vulkan -i vp90-2-05-resize.ivf -f 
framemd5 -` with `-c:v vp9`. I tested on Intel Arc (Meteor Lake) with Mesa 
26.0.8 and an RTX 5070 Ti with NVIDIA 595.91.07.



>From 24507a1ab28c77304427fa420e52f1ccfbe0cba1 Mon Sep 17 00:00:00 2001
From: Marcos Ashton Iglesias <[email protected]>
Date: Thu, 22 Oct 2026 16:51:19 +0100
Subject: [PATCH] avcodec/vp9: keep the hwaccel on frame size changes that fit

update_size() renegotiated the hwaccel on every frame size change, which
creates a new decoder instance. Frames other than keyframes can change
size and still predict from the reference frames and probability
contexts, which the hwaccel keeps state for. With Vulkan, every frame
after the first size change in vp90-2-05-resize.ivf came out wrong, and
the reference slots were not active in the new video session
(VUID-vkCmdBeginVideoCodingKHR-slotIndex-07239).

Keep the hwaccel on frames other than keyframes whose size fits the one
it was negotiated for. Keyframes still renegotiate, as does a frame that
grows beyond that size, which stays broken.
---
 libavcodec/vp9.c    | 15 +++++++++++++--
 libavcodec/vp9dec.h |  2 ++
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/libavcodec/vp9.c b/libavcodec/vp9.c
index c20b1002c11..54aaf4c61f6 100644
--- a/libavcodec/vp9.c
+++ b/libavcodec/vp9.c
@@ -187,7 +187,14 @@ static int update_size(AVCodecContext *avctx, int w, int h)
         changed = 1;
         if ((ret = ff_set_dimensions(avctx, w, h)) < 0)
             return ret;
+    }
 
+    // Frames other than keyframes can change size and still use the reference
+    // frames and probability contexts, which the hwaccel keeps state for, so
+    // keep it as long as the new size fits its surfaces
+    if (changed && !(avctx->hwaccel && !s->s.h.keyframe &&
+                     s->pix_fmt == s->gf_fmt &&
+                     w <= s->hwaccel_w && h <= s->hwaccel_h)) {
         switch (s->pix_fmt) {
         case AV_PIX_FMT_YUV420P:
         case AV_PIX_FMT_YUV420P10:
@@ -270,9 +277,11 @@ static int update_size(AVCodecContext *avctx, int w, int h)
 
         avctx->pix_fmt = ret;
         s->gf_fmt  = s->pix_fmt;
-        s->w = w;
-        s->h = h;
+        s->hwaccel_w = w;
+        s->hwaccel_h = h;
     }
+    s->w = w;
+    s->h = h;
 
     cols = (w + 7) >> 3;
     rows = (h + 7) >> 3;
@@ -1935,6 +1944,8 @@ static int 
vp9_decode_update_thread_context(AVCodecContext *dst, const AVCodecCo
     s->gf_fmt = ssrc->gf_fmt;
     s->w = ssrc->w;
     s->h = ssrc->h;
+    s->hwaccel_w = ssrc->hwaccel_w;
+    s->hwaccel_h = ssrc->hwaccel_h;
     s->s.h.bpp = ssrc->s.h.bpp;
     s->bpp_index = ssrc->bpp_index;
     s->pix_fmt = ssrc->pix_fmt;
diff --git a/libavcodec/vp9dec.h b/libavcodec/vp9dec.h
index fd33d03c2b4..921d013b3a5 100644
--- a/libavcodec/vp9dec.h
+++ b/libavcodec/vp9dec.h
@@ -124,6 +124,8 @@ typedef struct VP9Context {
     // and are therefore per-stream. pix_fmt represents the value in the header
     // of the currently processed frame.
     int w, h;
+    // size of the last get_format() negotiation, synced like w/h
+    int hwaccel_w, hwaccel_h;
     enum AVPixelFormat pix_fmt, last_fmt, gf_fmt;
     unsigned sb_cols, sb_rows, rows, cols;
     ProgressFrame next_refs[8];
-- 
2.54.0

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

Reply via email to