PR #25023 opened by Graham Vosloo (GrahamV)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25023
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25023.patch

# Summary of changes

Two fixes for the DXV3 decoder, found while comparing its output with
files written by Resolume Alley 7.28.1. Each commit adds FATE tests with
small clips made by Alley.

1. avcodec/dxv: fix opcode limit for heights that are not multiples of 16

   YCG6 and YG10 frames are padded to a multiple of 16 in both
   dimensions, and the opcode streams cover the padded area. The decoder
   computed the limit on the number of opcodes from the unpadded size,
   so files such as 1920x1080 failed to decode. The limit is now
   computed from the padded dimensions.

2. avcodec/dxv: do not unpremultiply DXT5 textures of DXV3

   Alley stores straight (non-premultiplied) alpha in DXT5 textures of
   DXV3 and writes the colour unchanged, but the decoder treated them as
   premultiplied and divided the colour by the alpha. The older
   headerless flavour of the format is left as it was.

Tested with `make fate` on AArch64 (macOS). Beyond FATE, the patched
decoder was run over 102 files made by Alley (about 386,000 frames)
without errors, and Normal Quality frames are unchanged.

```fate-samples
dxv/dxv3-hqna-padded.mov
dxv/dxv3-hqwa-padded.mov
dxv/dxv3-nqwa-alpha.mov
```# Summary of changes




>From fcd7732bdb865a9b741b8fc5ec809a8271abf22f Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 13:28:10 -0400
Subject: [PATCH 1/2] avcodec/dxv: fix opcode limit for heights that are not
 multiples of 16

Resolume pads the textures of a frame to multiples of 16 pixels in both
directions, and the YCG6 and YG10 formats can have one opcode for every
block of the padded textures. The decoder only aligns the height to 4
and derives the maximum number of opcodes from that, so a 1920x1080
frame whose opcodes cover the 1088 padded lines is rejected as invalid
as soon as nearly all of its blocks need an opcode, which happens with
noisy or highly detailed content.

Compute the limits from the padded dimensions instead. The textures
themselves are still decoded up to the coded height only.

Add tests with clips of 128x72 pixels made by Resolume Alley, which could
not be decoded.

Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
 libavcodec/dxv.c                | 12 ++++++++----
 tests/fate/video.mak            |  6 ++++++
 tests/ref/fate/dxv3-ycg6-padded |  7 +++++++
 tests/ref/fate/dxv3-yg10-padded |  7 +++++++
 4 files changed, 28 insertions(+), 4 deletions(-)
 create mode 100644 tests/ref/fate/dxv3-ycg6-padded
 create mode 100644 tests/ref/fate/dxv3-yg10-padded

diff --git a/libavcodec/dxv.c b/libavcodec/dxv.c
index bae46cdee0..894308cc18 100644
--- a/libavcodec/dxv.c
+++ b/libavcodec/dxv.c
@@ -992,16 +992,20 @@ static int dxv_decode(AVCodecContext *avctx, AVFrame 
*frame,
         memset(ctx->tex_data + old_size, 0, ctx->tex_data_size - old_size);
 
     if (avctx->pix_fmt != AV_PIX_FMT_RGBA) {
+        /* Resolume pads its textures to multiples of 16 pixels, and there
+         * can be one opcode for every block of the padded textures. */
+        const int padded_size = FFALIGN(avctx->coded_width,  16) *
+                                FFALIGN(avctx->coded_height, 16);
         int i;
 
         ctx->ctex_size = avctx->coded_width  / 2 / ctexdsp_ctx.raw_ratio *
                          avctx->coded_height / 2 / TEXTURE_BLOCK_H *
                          ctexdsp_ctx.tex_ratio;
 
-        ctx->op_size[0] = avctx->coded_width * avctx->coded_height / 16;
-        ctx->op_size[1] = avctx->coded_width * avctx->coded_height / 32;
-        ctx->op_size[2] = avctx->coded_width * avctx->coded_height / 32;
-        ctx->op_size[3] = avctx->coded_width * avctx->coded_height / 16;
+        ctx->op_size[0] = padded_size / 16;
+        ctx->op_size[1] = padded_size / 32;
+        ctx->op_size[2] = padded_size / 32;
+        ctx->op_size[3] = padded_size / 16;
 
         old_size = ctx->ctex_data_size;
         ptr = av_fast_realloc(ctx->ctex_data, &ctx->ctex_data_size, 
ctx->ctex_size + AV_INPUT_BUFFER_PADDING_SIZE);
diff --git a/tests/fate/video.mak b/tests/fate/video.mak
index fa7b202052..e5811b7016 100644
--- a/tests/fate/video.mak
+++ b/tests/fate/video.mak
@@ -158,6 +158,12 @@ fate-dxv3-ycg6: CMD = framecrc -i 
$(TARGET_SAMPLES)/dxv/dxv3-hqna.mov
 FATE_DXV += fate-dxv3-yg10
 fate-dxv3-yg10: CMD = framecrc -i $(TARGET_SAMPLES)/dxv/dxv3-hqwa.mov
 
+FATE_DXV += fate-dxv3-ycg6-padded
+fate-dxv3-ycg6-padded: CMD = framecrc -i 
$(TARGET_SAMPLES)/dxv/dxv3-hqna-padded.mov
+
+FATE_DXV += fate-dxv3-yg10-padded
+fate-dxv3-yg10-padded: CMD = framecrc -i 
$(TARGET_SAMPLES)/dxv/dxv3-hqwa-padded.mov
+
 FATE_VIDEO-$(call FRAMECRC, MOV, DXV) += $(FATE_DXV)
 fate-dxv: $(FATE_DXV)
 
diff --git a/tests/ref/fate/dxv3-ycg6-padded b/tests/ref/fate/dxv3-ycg6-padded
new file mode 100644
index 0000000000..8004ff9ab3
--- /dev/null
+++ b/tests/ref/fate/dxv3-ycg6-padded
@@ -0,0 +1,7 @@
+#tb 0: 1/30
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 128x72
+#sar 0: 0/1
+0,          0,          0,        1,    13824, 0x7e856094
+0,          1,          1,        1,    13824, 0x1a73e899
diff --git a/tests/ref/fate/dxv3-yg10-padded b/tests/ref/fate/dxv3-yg10-padded
new file mode 100644
index 0000000000..b31d3bfa11
--- /dev/null
+++ b/tests/ref/fate/dxv3-yg10-padded
@@ -0,0 +1,7 @@
+#tb 0: 1/30
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 128x72
+#sar 0: 0/1
+0,          0,          0,        1,    23040, 0xdd903eb0
+0,          1,          1,        1,    23040, 0x4c7cc6b5
-- 
2.52.0


>From 54a905b49979fb660a070ae36dd79b494d4a16fc Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 13:28:10 -0400
Subject: [PATCH 2/2] avcodec/dxv: do not unpremultiply DXT5 textures of DXV3

The decoder assumed that the color of DXV3 DXT5 textures is
premultiplied by alpha, and divided it by alpha. Resolume's encoder
does not premultiply anything though: Resolume Alley 7.28.1 writes the
color of a source with straight alpha into the texture unchanged, as
can be seen by converting the same source with its "Normal Quality No
Alpha" and "Normal Quality With Alpha" presets and comparing the
textures. Dividing by alpha then blows the colors out wherever alpha
is not 255. The YG10 format, which also has alpha, was already output
as is.

The FATE sample is fully opaque, so its reference does not change.
The older, headerless DXT5 flavour is left alone as there is no encoder
at hand to check it against.

Add a test with a clip made by Resolume Alley whose alpha is not
constant.

Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
 libavcodec/dxv.c               | 5 +++--
 tests/fate/video.mak           | 3 +++
 tests/ref/fate/dxv3-dxt5-alpha | 7 +++++++
 3 files changed, 13 insertions(+), 2 deletions(-)
 create mode 100644 tests/ref/fate/dxv3-dxt5-alpha

diff --git a/libavcodec/dxv.c b/libavcodec/dxv.c
index 894308cc18..0b4b107d0e 100644
--- a/libavcodec/dxv.c
+++ b/libavcodec/dxv.c
@@ -879,8 +879,9 @@ static int dxv_decode(AVCodecContext *avctx, AVFrame *frame,
         break;
     case DXV_FMT_DXT5:
         decompress_tex = dxv_decompress_dxt5;
-        /* DXV misnomers DXT5, alpha is premultiplied so use DXT4 instead */
-        texdsp_ctx.tex_funct = ctx->texdsp.dxt4_block;
+        /* Resolume stores the color of its input as is, whether its alpha
+         * is straight or premultiplied, so do not alter it either. */
+        texdsp_ctx.tex_funct = ctx->texdsp.dxt5_block;
         texdsp_ctx.tex_ratio = 16;
         texdsp_ctx.raw_ratio = 16;
         msgcomp = "DXTR5";
diff --git a/tests/fate/video.mak b/tests/fate/video.mak
index e5811b7016..c6c0d419ba 100644
--- a/tests/fate/video.mak
+++ b/tests/fate/video.mak
@@ -164,6 +164,9 @@ fate-dxv3-ycg6-padded: CMD = framecrc -i 
$(TARGET_SAMPLES)/dxv/dxv3-hqna-padded.
 FATE_DXV += fate-dxv3-yg10-padded
 fate-dxv3-yg10-padded: CMD = framecrc -i 
$(TARGET_SAMPLES)/dxv/dxv3-hqwa-padded.mov
 
+FATE_DXV += fate-dxv3-dxt5-alpha
+fate-dxv3-dxt5-alpha: CMD = framecrc -i 
$(TARGET_SAMPLES)/dxv/dxv3-nqwa-alpha.mov
+
 FATE_VIDEO-$(call FRAMECRC, MOV, DXV) += $(FATE_DXV)
 fate-dxv: $(FATE_DXV)
 
diff --git a/tests/ref/fate/dxv3-dxt5-alpha b/tests/ref/fate/dxv3-dxt5-alpha
new file mode 100644
index 0000000000..bb4c72a720
--- /dev/null
+++ b/tests/ref/fate/dxv3-dxt5-alpha
@@ -0,0 +1,7 @@
+#tb 0: 1/30
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 128x64
+#sar 0: 0/1
+0,          0,          0,        1,    32768, 0xaf2dca4e
+0,          1,          1,        1,    32768, 0x9a90a39c
-- 
2.52.0

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

Reply via email to