PR #25025 opened by Graham Vosloo (GrahamV)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25025
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25025.patch
# Summary of changes
Adds assembly for three of the texture block decoders, with a checkasm
test:
- rgtc1u_gray_block: YCG6 and YG10 of DXV, alpha-only Hap
- dxt1_block: DXT1 of DXV, Hap and VBN
- dxt5_block: DXT5 of Hap Alpha, VBN and DDS
Each has a NEON version and an SSSE3 version (x86-64 only). They are
bit-exact with the C functions; the checkasm test covers every value of
the reference values and colors, and every index at every position.
checkasm --bench, speed-up over C:
```
Apple M4 Pro Intel Core i9-11900K
rgtc1u_gray_block 10.8x 4.2x
dxt1_block 1.8x 6.4x
dxt5_block 5.5x 10.0x
```
Decoding 3680x2160 with one thread on the M4 Pro: DXV YCG6 goes from
149 to 345 frames per second, DXV DXT1 from 326 to 500, and VBN DXT5
from 200 to 632.
Tested with `make fate` on AArch64 (macOS); checkasm and the DXV, Hap,
DDS and VBN tests also pass in an x86-64 build run under Rosetta 2, and
checkasm passes on the Intel machine above (Windows).
>From d509fc6cdb16300f1ece04e998cbb29085024ba4 Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 16:37:08 -0400
Subject: [PATCH 1/8] tests/checkasm: add a test for texturedsp
Only rgtc1u_gray_block is tested for now, as it is the only function
that is about to be optimised. It is checked with every pair of
reference values, since the number of interpolated values depends on
their order, and with every index at every position.
Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
tests/checkasm/Makefile | 1 +
tests/checkasm/checkasm.c | 3 ++
tests/checkasm/checkasm.h | 1 +
tests/checkasm/texturedsp.c | 104 ++++++++++++++++++++++++++++++++++++
tests/fate/checkasm.mak | 1 +
5 files changed, 110 insertions(+)
create mode 100644 tests/checkasm/texturedsp.c
diff --git a/tests/checkasm/Makefile b/tests/checkasm/Makefile
index 062058f778..e088c6ede2 100644
--- a/tests/checkasm/Makefile
+++ b/tests/checkasm/Makefile
@@ -25,6 +25,7 @@ AVCODECOBJS-$(CONFIG_ME_CMP) += motion.o
AVCODECOBJS-$(CONFIG_MPEGVIDEO) += mpegvideo_unquantize.o
AVCODECOBJS-$(CONFIG_MPEGVIDEOENCDSP) += mpegvideoencdsp.o
AVCODECOBJS-$(CONFIG_QPELDSP) += qpeldsp.o
+AVCODECOBJS-$(CONFIG_TEXTUREDSP) += texturedsp.o
AVCODECOBJS-$(CONFIG_VC1DSP) += vc1dsp.o
AVCODECOBJS-$(CONFIG_VP3DSP) += vp3dsp.o
AVCODECOBJS-$(CONFIG_VP8DSP) += vp8dsp.o
diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c
index 3d34df656e..5010db92df 100644
--- a/tests/checkasm/checkasm.c
+++ b/tests/checkasm/checkasm.c
@@ -202,6 +202,9 @@ static const CheckasmTest tests[] = {
#if CONFIG_TAK_DECODER
{ "takdsp", checkasm_check_takdsp },
#endif
+ #if CONFIG_TEXTUREDSP
+ { "texturedsp", checkasm_check_texturedsp },
+ #endif
#if CONFIG_TTA_DECODER
{ "ttadsp", checkasm_check_ttadsp },
#endif
diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h
index e45befb05b..057ac081d7 100644
--- a/tests/checkasm/checkasm.h
+++ b/tests/checkasm/checkasm.h
@@ -107,6 +107,7 @@ void checkasm_check_sw_yuv2rgb(void);
void checkasm_check_sw_yuv2yuv(void);
void checkasm_check_sw_ops(void);
void checkasm_check_takdsp(void);
+void checkasm_check_texturedsp(void);
void checkasm_check_ttadsp(void);
void checkasm_check_ttaencdsp(void);
void checkasm_check_utvideodsp(void);
diff --git a/tests/checkasm/texturedsp.c b/tests/checkasm/texturedsp.c
new file mode 100644
index 0000000000..9aa7d7d2f7
--- /dev/null
+++ b/tests/checkasm/texturedsp.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2026 Graham Vosloo <[email protected]>
+ *
+ * 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 <stddef.h>
+#include <string.h>
+
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem_internal.h"
+#include "libavcodec/texturedsp.h"
+
+#include "checkasm.h"
+
+/* The blocks are decoded to the middle of lines that are wider than them,
+ * and the lines are surrounded by other lines, to check that nothing else
+ * is written. */
+#define STRIDE 32
+#define BUF_SIZE (STRIDE * (TEXTURE_BLOCK_H + 2))
+#define DST_OFF (STRIDE + 8)
+
+#define randomize_buf(buf, size) \
+ do { \
+ for (int i = 0; i < size; i++) \
+ buf[i] = (uint8_t)rnd(); \
+ } while (0)
+
+#define check_block() \
+ do { \
+ int ret0, ret1; \
+ randomize_buf(dst0, BUF_SIZE); \
+ memcpy(dst1, dst0, BUF_SIZE); \
+ ret0 = call_ref(dst0 + DST_OFF, STRIDE, block); \
+ ret1 = call_new(dst1 + DST_OFF, STRIDE, block); \
+ if (ret0 != ret1) \
+ fail(); \
+ checkasm_check(uint8_t, dst0, STRIDE, dst1, STRIDE, \
+ STRIDE, TEXTURE_BLOCK_H + 2, "dst"); \
+ } while (0)
+
+/* Blocks made of two reference values followed by 16 indices of 3 bits. */
+static void check_rgtc1(int (*func)(uint8_t *dst, ptrdiff_t stride, const
uint8_t *block),
+ const char *name)
+{
+ LOCAL_ALIGNED_16(uint8_t, dst0, [BUF_SIZE]);
+ LOCAL_ALIGNED_16(uint8_t, dst1, [BUF_SIZE]);
+ /* The block is not aligned, and ends at the end of the buffer. */
+ uint8_t buf[16];
+ uint8_t *const block = buf + 8;
+
+ declare_func(int, uint8_t *dst, ptrdiff_t stride, const uint8_t *block);
+
+ if (check_func(func, "%s", name)) {
+ /* Every pair of reference values, as the interpolation depends on
+ * their order, with random indices */
+ for (int ref = 0; ref < 65536; ref++) {
+ AV_WL16(block, ref);
+ AV_WL16(block + 2, rnd());
+ AV_WL32(block + 4, rnd());
+ check_block();
+ }
+
+ /* Every index at every position, with the reference values in
+ * both orders */
+ for (int i = 0; i < 8; i++) {
+ uint64_t indices = 0;
+
+ for (int j = 0; j < 16; j++)
+ indices |= (uint64_t)((i + j) & 7) << (3 * j);
+ AV_WL64(block, indices << 16 | (rnd() & 0xffff));
+ check_block();
+ AV_WL16(block, (block[0] << 8) | block[1]);
+ check_block();
+ }
+
+ AV_WL64(block, (uint64_t)rnd() << 32 | rnd());
+ bench_new(dst1 + DST_OFF, STRIDE, block);
+ }
+}
+
+void checkasm_check_texturedsp(void)
+{
+ TextureDSPContext c;
+
+ ff_texturedsp_init(&c);
+
+ check_rgtc1(c.rgtc1u_gray_block, "rgtc1u_gray_block");
+ report("rgtc1u_gray_block");
+}
diff --git a/tests/fate/checkasm.mak b/tests/fate/checkasm.mak
index 0ceebbe3d1..e9b1e40978 100644
--- a/tests/fate/checkasm.mak
+++ b/tests/fate/checkasm.mak
@@ -70,6 +70,7 @@ FATE_CHECKASM = fate-checkasm-aacencdsp
\
fate-checkasm-sw_yuv2rgb \
fate-checkasm-sw_yuv2yuv \
fate-checkasm-takdsp \
+ fate-checkasm-texturedsp \
fate-checkasm-ttadsp \
fate-checkasm-ttaencdsp \
fate-checkasm-utvideodsp \
--
2.52.0
>From c7d16a0538172a017dc1e967cb486ba364c5dd9e Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 16:38:56 -0400
Subject: [PATCH 2/8] avcodec/aarch64/texturedsp: add NEON rgtc1u_gray_block
The blocks of the YCG6 and YG10 formats of DXV, and of the alpha-only
format of Hap, are decoded with this function.
The 8 values of a block are interpolated at once, with multiplications
that divide exactly by 7 or 5, and the 16 indices are extracted from
the 6 bytes holding them with two table lookups and two shifts.
On an Apple M4 Pro, with a timer that is too coarse for the new
function, and a C time that goes from 4.4 to 6.4 depending on the run:
rgtc1u_gray_block_c: 4.8
rgtc1u_gray_block_neon: 0.4 (10.81x)
Decoding 600 frames of 3680x2160 YCG6 made by Resolume Alley goes from
149 to 345 frames per second with one thread (best of 5 runs).
Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
libavcodec/aarch64/Makefile | 2 +
libavcodec/aarch64/texturedsp_init.c | 37 ++++++++++++
libavcodec/aarch64/texturedsp_neon.S | 88 ++++++++++++++++++++++++++++
libavcodec/texturedsp.c | 6 ++
libavcodec/texturedsp.h | 1 +
5 files changed, 134 insertions(+)
create mode 100644 libavcodec/aarch64/texturedsp_init.c
create mode 100644 libavcodec/aarch64/texturedsp_neon.S
diff --git a/libavcodec/aarch64/Makefile b/libavcodec/aarch64/Makefile
index e0762e3fa1..7055940aba 100644
--- a/libavcodec/aarch64/Makefile
+++ b/libavcodec/aarch64/Makefile
@@ -14,6 +14,7 @@ OBJS-$(CONFIG_MPEGVIDEOENCDSP) +=
aarch64/mpegvideoencdsp_init.o
OBJS-$(CONFIG_NEON_CLOBBER_TEST) += aarch64/neontest.o
OBJS-$(CONFIG_PIXBLOCKDSP) += aarch64/pixblockdsp_init_aarch64.o
OBJS-$(CONFIG_PNG_DECODER) += aarch64/pngdsp_init.o
+OBJS-$(CONFIG_TEXTUREDSP) += aarch64/texturedsp_init.o
OBJS-$(CONFIG_VIDEODSP) += aarch64/videodsp_init.o
OBJS-$(CONFIG_VP8DSP) += aarch64/vp8dsp_init_aarch64.o
@@ -59,6 +60,7 @@ NEON-OBJS-$(CONFIG_MPEGAUDIODSP) +=
aarch64/mpegaudiodsp_neon.o
NEON-OBJS-$(CONFIG_MPEGVIDEOENCDSP) += aarch64/mpegvideoencdsp_neon.o
NEON-OBJS-$(CONFIG_PIXBLOCKDSP) += aarch64/pixblockdsp_neon.o
NEON-OBJS-$(CONFIG_PNG_DECODER) += aarch64/pngdsp_neon.o
+NEON-OBJS-$(CONFIG_TEXTUREDSP) += aarch64/texturedsp_neon.o
NEON-OBJS-$(CONFIG_VC1DSP) += aarch64/vc1dsp_neon.o
NEON-OBJS-$(CONFIG_VP8DSP) += aarch64/vp8dsp_neon.o
diff --git a/libavcodec/aarch64/texturedsp_init.c
b/libavcodec/aarch64/texturedsp_init.c
new file mode 100644
index 0000000000..96865bd696
--- /dev/null
+++ b/libavcodec/aarch64/texturedsp_init.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2026 Graham Vosloo <[email protected]>
+ *
+ * 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 <stddef.h>
+#include <stdint.h>
+
+#include "libavutil/attributes.h"
+#include "libavutil/aarch64/cpu.h"
+#include "libavcodec/texturedsp.h"
+
+int ff_rgtc1u_gray_block_neon(uint8_t *dst, ptrdiff_t stride,
+ const uint8_t *block);
+
+av_cold void ff_texturedsp_init_aarch64(TextureDSPContext *c)
+{
+ int cpu_flags = av_get_cpu_flags();
+
+ if (have_neon(cpu_flags))
+ c->rgtc1u_gray_block = ff_rgtc1u_gray_block_neon;
+}
diff --git a/libavcodec/aarch64/texturedsp_neon.S
b/libavcodec/aarch64/texturedsp_neon.S
new file mode 100644
index 0000000000..46ebefd0f5
--- /dev/null
+++ b/libavcodec/aarch64/texturedsp_neon.S
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2026 Graham Vosloo <[email protected]>
+ *
+ * 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 "libavutil/aarch64/asm.S"
+
+const rgtc1_tab
+ // Bytes of the block holding the two reference values, for the
+ // multiplications with the weights
+ .byte 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1
+ // Byte holding the lowest bit of each index, and the next one
+ .byte 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6,
6, 7, 7
+ .byte 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7,
7, 8, 8
+ // Shifts moving the index of each of these two bytes to bits 1-3
+ .byte 1, -2, -5, 0, -3, -6, -1, -4, 1, -2, -5, 0, -3,
-6, -1, -4
+ .byte 9, 6, 3, 8, 5, 2, 7, 4, 9, 6, 3, 8, 5,
2, 7, 4
+ // 4 interpolated values, 0 and 255: weights of the reference values,
+ // reciprocal of their sum, and the value of the last entry
+ .byte 5, 0, 4, 3, 2, 1, 0, 0, 0, 5, 1, 2, 3,
4, 0, 0
+ .short 6554, 6554, 6554, 6554, 6554, 6554, 6554, 6554
+ .short 0, 0, 0, 0, 0, 0, 0, 255
+ // 6 interpolated values
+ .byte 7, 0, 6, 5, 4, 3, 2, 1, 0, 7, 1, 2, 3,
4, 5, 6
+ .short 4682, 4682, 4682, 4682, 4682, 4682, 4682, 4682
+ .short 0, 0, 0, 0, 0, 0, 0, 0
+endconst
+
+/* int ff_rgtc1u_gray_block_neon(uint8_t *dst, ptrdiff_t stride,
+ * const uint8_t *block);
+ * x0: dst
+ * x1: stride
+ * x2: block
+ */
+function ff_rgtc1u_gray_block_neon, export=1
+ ldrb w3, [x2]
+ ldrb w5, [x2, #1]
+ movrel x4, rgtc1_tab
+ ld1 {v0.8b}, [x2]
+ ld1 {v16.16b - v19.16b}, [x4], #64
+ cmp w3, w5
+ ld1 {v20.16b}, [x4], #16
+ add x5, x4, #48
+ movi v7.16b, #0x0e
+ // There are 6 interpolated values if the first reference value is
+ // larger than the second one, and 4 otherwise.
+ csel x4, x5, x4, hi
+ ld1 {v21.16b - v23.16b}, [x4]
+
+ // The 8 values are computed as 16-bit elements: the weighted sum of
+ // the reference values is at most 7 * 255, for which the
+ // multiplications by 4682 / 32768 and 6554 / 32768 are exact
+ // divisions by 7 and 5. The indices are doubled to select the low
+ // bytes of the values.
+ tbl v1.16b, {v0.16b}, v16.16b
+ tbl v2.16b, {v0.16b}, v17.16b
+ tbl v3.16b, {v0.16b}, v18.16b
+ umull v4.8h, v1.8b, v21.8b
+ ushl v2.16b, v2.16b, v19.16b
+ ushl v3.16b, v3.16b, v20.16b
+ umlal2 v4.8h, v1.16b, v21.16b
+ orr v2.16b, v2.16b, v3.16b
+ sqdmulh v4.8h, v4.8h, v22.8h
+ and v2.16b, v2.16b, v7.16b // 2 * index
+ orr v4.16b, v4.16b, v23.16b // values
+ tbl v0.16b, {v4.16b}, v2.16b
+ st1 {v0.s}[0], [x0], x1
+ st1 {v0.s}[1], [x0], x1
+ st1 {v0.s}[2], [x0], x1
+ st1 {v0.s}[3], [x0]
+ mov w0, #8
+ ret
+endfunc
diff --git a/libavcodec/texturedsp.c b/libavcodec/texturedsp.c
index a217b22174..648dfab31e 100644
--- a/libavcodec/texturedsp.c
+++ b/libavcodec/texturedsp.c
@@ -26,6 +26,8 @@
#include <stddef.h>
#include <stdint.h>
+#include "config.h"
+
#include "libavutil/attributes.h"
#include "libavutil/common.h"
#include "libavutil/intreadwrite.h"
@@ -654,6 +656,10 @@ av_cold void ff_texturedsp_init(TextureDSPContext *c)
c->rgtc2s_block = rgtc2s_block;
c->rgtc2u_block = rgtc2u_block;
c->dxn3dc_block = dxn3dc_block;
+
+#if ARCH_AARCH64
+ ff_texturedsp_init_aarch64(c);
+#endif
}
#define TEXTUREDSP_FUNC_NAME ff_texturedsp_exec_decompress_threads
diff --git a/libavcodec/texturedsp.h b/libavcodec/texturedsp.h
index 0bf49e8729..136c654880 100644
--- a/libavcodec/texturedsp.h
+++ b/libavcodec/texturedsp.h
@@ -86,6 +86,7 @@ typedef struct TextureDSPThreadContext {
} TextureDSPThreadContext;
void ff_texturedsp_init(TextureDSPContext *c);
+void ff_texturedsp_init_aarch64(TextureDSPContext *c);
void ff_texturedspenc_init(TextureDSPEncContext *c);
struct AVCodecContext;
--
2.52.0
>From 53b9fe37c262e6fadd784a8868407987e82e643c Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 17:12:44 -0400
Subject: [PATCH 3/8] avcodec/x86/texturedsp: add SSSE3 rgtc1u_gray_block
It follows the NEON version. The 8 values of a block are interpolated
at once: pmaddubsw computes the weighted sums of the two reference
values, and pmulhuw divides them exactly by 7 or 5, the sums being at
most 7 * 255. The constants of the two modes are selected with cmov.
As there is no shift of bytes by variable amounts, the 16 indices are
extracted as 16-bit elements: pshufb gathers the two bytes holding
each of them, a multiplication by a power of two moves it to the 3
highest bits, and a shift brings it down. A last pshufb maps the
indices to the values.
It is limited to x86-64. On an Intel Core i9-11900K:
rgtc1u_gray_block_c: 32.5
rgtc1u_gray_block_ssse3: 5.5 ( 4.20x)
Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
libavcodec/texturedsp.c | 2 +
libavcodec/texturedsp.h | 1 +
libavcodec/x86/Makefile | 2 +
libavcodec/x86/texturedsp.asm | 89 ++++++++++++++++++++++++++++++++
libavcodec/x86/texturedsp_init.c | 41 +++++++++++++++
5 files changed, 135 insertions(+)
create mode 100644 libavcodec/x86/texturedsp.asm
create mode 100644 libavcodec/x86/texturedsp_init.c
diff --git a/libavcodec/texturedsp.c b/libavcodec/texturedsp.c
index 648dfab31e..14e63d7262 100644
--- a/libavcodec/texturedsp.c
+++ b/libavcodec/texturedsp.c
@@ -659,6 +659,8 @@ av_cold void ff_texturedsp_init(TextureDSPContext *c)
#if ARCH_AARCH64
ff_texturedsp_init_aarch64(c);
+#elif ARCH_X86 && HAVE_X86ASM
+ ff_texturedsp_init_x86(c);
#endif
}
diff --git a/libavcodec/texturedsp.h b/libavcodec/texturedsp.h
index 136c654880..4afad737b6 100644
--- a/libavcodec/texturedsp.h
+++ b/libavcodec/texturedsp.h
@@ -87,6 +87,7 @@ typedef struct TextureDSPThreadContext {
void ff_texturedsp_init(TextureDSPContext *c);
void ff_texturedsp_init_aarch64(TextureDSPContext *c);
+void ff_texturedsp_init_x86(TextureDSPContext *c);
void ff_texturedspenc_init(TextureDSPEncContext *c);
struct AVCodecContext;
diff --git a/libavcodec/x86/Makefile b/libavcodec/x86/Makefile
index 456a209482..f61fd191f1 100644
--- a/libavcodec/x86/Makefile
+++ b/libavcodec/x86/Makefile
@@ -33,6 +33,7 @@ OBJS-$(CONFIG_MPEGVIDEOENCDSP) +=
x86/mpegvideoencdsp_init.o
X86ASM-OBJS-$(CONFIG_PIXBLOCKDSP) += x86/pixblockdsp_init.o
X86ASM-OBJS-$(CONFIG_QPELDSP) += x86/qpeldsp_init.o
X86ASM-OBJS-$(CONFIG_RV34DSP) += x86/rv34dsp_init.o
+X86ASM-OBJS-$(CONFIG_TEXTUREDSP) += x86/texturedsp_init.o
X86ASM-OBJS-$(CONFIG_VC1DSP) += x86/vc1dsp_init.o x86/vc1dsp_mmx.o
X86ASM-OBJS-$(CONFIG_VIDEODSP) += x86/videodsp_init.o
X86ASM-OBJS-$(CONFIG_VP3DSP) += x86/vp3dsp_init.o
@@ -128,6 +129,7 @@ X86ASM-OBJS-$(CONFIG_QPELDSP) += x86/qpeldsp.o
\
x86/fpel.o \
x86/qpel.o
X86ASM-OBJS-$(CONFIG_RV34DSP) += x86/rv34dsp.o
+X86ASM-OBJS-$(CONFIG_TEXTUREDSP) += x86/texturedsp.o
X86ASM-OBJS-$(CONFIG_VC1DSP) += x86/vc1dsp_inv_trans.o \
x86/vc1dsp_loopfilter.o \
x86/vc1dsp_mc.o x86/fpel.o
diff --git a/libavcodec/x86/texturedsp.asm b/libavcodec/x86/texturedsp.asm
new file mode 100644
index 0000000000..d5c5000ea9
--- /dev/null
+++ b/libavcodec/x86/texturedsp.asm
@@ -0,0 +1,89 @@
+;******************************************************************************
+;* x86 optimizations for texture block decompression
+;* Copyright (c) 2026 Graham Vosloo <[email protected]>
+;*
+;* 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 "libavutil/x86/x86util.asm"
+
+%if ARCH_X86_64
+
+SECTION_RODATA
+
+; Bytes of the block holding the two reference values, for the multiplications
+; with the weights
+rgtc1_refs: times 8 db 0, 1
+; The two bytes holding each index
+rgtc1_idx_lo: db 2, 3, 2, 3, 2, 3, 3, 4, 3, 4, 3, 4, 4, 5, 4, 5
+rgtc1_idx_hi: db 5, 6, 5, 6, 5, 6, 6, 7, 6, 7, 6, 7, 7, -1, 7, -1
+; Multipliers moving each index to the 3 highest bits
+rgtc1_idx_mul: dw 1 << 13, 1 << 10, 1 << 7, 1 << 12, 1 << 9, 1 << 6, 1 << 11,
1 << 8
+; 6 interpolated values: weights of the reference values, reciprocal of their
+; sum, and the values that are not interpolated
+rgtc1_interp6: db 7, 0, 0, 7, 6, 1, 5, 2, 4, 3, 3, 4, 2, 5, 1, 6
+ times 8 dw 9363
+ times 8 dw 0
+; 4 interpolated values, 0 and 255
+rgtc1_interp4: db 5, 0, 0, 5, 4, 1, 3, 2, 2, 3, 1, 4, 0, 0, 0, 0
+ times 8 dw 13108
+ dw 0, 0, 0, 0, 0, 0, 0, 255
+
+SECTION .text
+
+;------------------------------------------------------------------------------
+; int ff_rgtc1u_gray_block(uint8_t *dst, ptrdiff_t stride, const uint8_t
*block)
+;------------------------------------------------------------------------------
+INIT_XMM ssse3
+cglobal rgtc1u_gray_block, 3, 5, 3, dst, stride, block, tab, tmp
+ movzx tmpd, byte [blockq]
+ movq m0, [blockq]
+ lea tabq, [rgtc1_interp4]
+ cmp tmpb, [blockq+1]
+ lea tmpq, [rgtc1_interp6]
+ ; There are 6 interpolated values if the first reference value is larger
+ ; than the second one, and 4 otherwise.
+ cmova tabq, tmpq
+
+ ; The 8 values are computed as 16-bit elements: the weighted sum of the
+ ; reference values is at most 7 * 255, for which the multiplications by
+ ; 9363 / 65536 and 13108 / 65536 are exact divisions by 7 and 5.
+ pshufb m1, m0, [rgtc1_refs]
+ pshufb m2, m0, [rgtc1_idx_lo]
+ pshufb m0, [rgtc1_idx_hi]
+ pmaddubsw m1, [tabq]
+ pmullw m2, [rgtc1_idx_mul]
+ pmullw m0, [rgtc1_idx_mul]
+ pmulhuw m1, [tabq+16]
+ psrlw m2, 13
+ psrlw m0, 13
+ por m1, [tabq+32]
+ packuswb m2, m0 ; indices
+ packuswb m1, m1 ; values
+ lea tmpq, [strideq*3]
+ pshufb m1, m2
+ movd [dstq], m1
+ psrldq m1, 4
+ movd [dstq+strideq], m1
+ psrldq m1, 4
+ movd [dstq+strideq*2], m1
+ psrldq m1, 4
+ movd [dstq+tmpq], m1
+ mov eax, 8
+ RET
+
+%endif ; ARCH_X86_64
diff --git a/libavcodec/x86/texturedsp_init.c b/libavcodec/x86/texturedsp_init.c
new file mode 100644
index 0000000000..76e70dfab4
--- /dev/null
+++ b/libavcodec/x86/texturedsp_init.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2026 Graham Vosloo <[email protected]>
+ *
+ * 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 <stddef.h>
+#include <stdint.h>
+
+#include "config.h"
+
+#include "libavutil/attributes.h"
+#include "libavutil/x86/cpu.h"
+#include "libavcodec/texturedsp.h"
+
+int ff_rgtc1u_gray_block_ssse3(uint8_t *dst, ptrdiff_t stride,
+ const uint8_t *block);
+
+av_cold void ff_texturedsp_init_x86(TextureDSPContext *c)
+{
+#if ARCH_X86_64
+ int cpu_flags = av_get_cpu_flags();
+
+ if (EXTERNAL_SSSE3(cpu_flags))
+ c->rgtc1u_gray_block = ff_rgtc1u_gray_block_ssse3;
+#endif
+}
--
2.52.0
>From 1b5e37045f8878bcdfb4340e1d57f23865b14c05 Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 18:27:00 -0400
Subject: [PATCH 4/8] tests/checkasm/texturedsp: test dxt1_block and dxt5_block
dxt1_block decodes the DXT1 format of DXV, Hap and VBN, and dxt5_block
the DXT5 textures of Hap Alpha, VBN and DDS.
Every value of each of the two colors of a block is checked with a
random value of the other one, and with its neighbours, as the
interpolation of DXT1 depends on the order of the colors, while that of
DXT5 must not. Every index is checked at every position, and so are the
alpha values of DXT5, in the way those of rgtc1u_gray_block already are.
Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
tests/checkasm/texturedsp.c | 81 +++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
diff --git a/tests/checkasm/texturedsp.c b/tests/checkasm/texturedsp.c
index 9aa7d7d2f7..a150d26179 100644
--- a/tests/checkasm/texturedsp.c
+++ b/tests/checkasm/texturedsp.c
@@ -93,12 +93,93 @@ static void check_rgtc1(int (*func)(uint8_t *dst, ptrdiff_t
stride, const uint8_
}
}
+/* Blocks made of two RGB565 colors followed by 16 indices of 2 bits. size is
+ * the size of the block, and off the offset of this part of it. */
+static void check_dxt(int (*func)(uint8_t *dst, ptrdiff_t stride, const
uint8_t *block),
+ const char *name, int size, int off)
+{
+ LOCAL_ALIGNED_16(uint8_t, dst0, [BUF_SIZE]);
+ LOCAL_ALIGNED_16(uint8_t, dst1, [BUF_SIZE]);
+ /* The block is not aligned, and ends at the end of the buffer. */
+ uint8_t buf[24];
+ uint8_t *const block = buf + sizeof(buf) - size;
+ uint8_t *const color = block + off;
+
+ declare_func(int, uint8_t *dst, ptrdiff_t stride, const uint8_t *block);
+
+ if (check_func(func, "%s", name)) {
+ /* Every value of each color with a random value of the other one,
+ * and next to itself, as the interpolation of DXT1 depends on their
+ * order */
+ for (int c = 0; c < 65536; c++) {
+ randomize_buf(block, size);
+ AV_WL16(color, c);
+ check_block();
+ AV_WL16(color + 2, c + 1);
+ check_block();
+ AV_WL16(color, AV_RL16(color + 4));
+ check_block();
+ AV_WL16(color, c + 2);
+ check_block();
+ }
+
+ /* The same color twice */
+ for (int i = 0; i < 1024; i++) {
+ randomize_buf(block, size);
+ AV_WL16(color + 2, AV_RL16(color));
+ check_block();
+ }
+
+ /* Every index at every position, with the colors in both orders */
+ for (int i = 0; i < 4; i++) {
+ uint32_t indices = 0;
+
+ for (int j = 0; j < 16; j++)
+ indices |= (uint32_t)((i + j) & 3) << (2 * j);
+ randomize_buf(block, size);
+ AV_WL32(color + 4, indices);
+ check_block();
+ AV_WL32(color, AV_RL16(color) << 16 | AV_RL16(color + 2));
+ check_block();
+ }
+
+ /* Blocks of DXT5 start with an RGTC1 block holding alpha. */
+ if (off) {
+ for (int ref = 0; ref < 65536; ref++) {
+ randomize_buf(block, size);
+ AV_WL16(block, ref);
+ check_block();
+ }
+
+ for (int i = 0; i < 8; i++) {
+ uint64_t indices = 0;
+
+ for (int j = 0; j < 16; j++)
+ indices |= (uint64_t)((i + j) & 7) << (3 * j);
+ AV_WL64(block, indices << 16 | (rnd() & 0xffff));
+ check_block();
+ AV_WL16(block, (block[0] << 8) | block[1]);
+ check_block();
+ }
+ }
+
+ randomize_buf(block, size);
+ bench_new(dst1 + DST_OFF, STRIDE, block);
+ }
+}
+
void checkasm_check_texturedsp(void)
{
TextureDSPContext c;
ff_texturedsp_init(&c);
+ check_dxt(c.dxt1_block, "dxt1_block", 8, 0);
+ report("dxt1_block");
+
+ check_dxt(c.dxt5_block, "dxt5_block", 16, 8);
+ report("dxt5_block");
+
check_rgtc1(c.rgtc1u_gray_block, "rgtc1u_gray_block");
report("rgtc1u_gray_block");
}
--
2.52.0
>From fb5bc38703ab796c17d5e71664cd51bc1c9b9fc1 Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 18:27:55 -0400
Subject: [PATCH 5/8] avcodec/aarch64/texturedsp: add NEON dxt1_block
This is the block decoder of the DXT1 format of DXV, Hap and VBN.
The components of the two colors are extracted and expanded as 16-bit
elements, with a multiplication that gives what the two divisions of
the C version do for all of their values. The interpolated colors are
computed for the two colors at once, with a reciprocal that is exact
for the sums involved, and the mode is chosen without a branch by
selecting the weights. The pixels of a line are then looked up in the
table of the four colors.
On an Apple M4 Pro, with a timer that is coarse for functions this
short, and a C time that goes from 2.8 to 3.4 depending on the run:
dxt1_block_c: 2.8
dxt1_block_neon: 1.5 ( 1.84x)
Decoding 600 frames of 3680x2160 DXV DXT1 made by Resolume Alley goes
from 326 to 500 frames per second with one thread (best of 5 runs).
Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
libavcodec/aarch64/texturedsp_init.c | 6 +-
libavcodec/aarch64/texturedsp_neon.S | 90 ++++++++++++++++++++++++++++
2 files changed, 95 insertions(+), 1 deletion(-)
diff --git a/libavcodec/aarch64/texturedsp_init.c
b/libavcodec/aarch64/texturedsp_init.c
index 96865bd696..3599fac4b7 100644
--- a/libavcodec/aarch64/texturedsp_init.c
+++ b/libavcodec/aarch64/texturedsp_init.c
@@ -25,6 +25,8 @@
#include "libavutil/aarch64/cpu.h"
#include "libavcodec/texturedsp.h"
+int ff_dxt1_block_neon(uint8_t *dst, ptrdiff_t stride,
+ const uint8_t *block);
int ff_rgtc1u_gray_block_neon(uint8_t *dst, ptrdiff_t stride,
const uint8_t *block);
@@ -32,6 +34,8 @@ av_cold void ff_texturedsp_init_aarch64(TextureDSPContext *c)
{
int cpu_flags = av_get_cpu_flags();
- if (have_neon(cpu_flags))
+ if (have_neon(cpu_flags)) {
+ c->dxt1_block = ff_dxt1_block_neon;
c->rgtc1u_gray_block = ff_rgtc1u_gray_block_neon;
+ }
}
diff --git a/libavcodec/aarch64/texturedsp_neon.S
b/libavcodec/aarch64/texturedsp_neon.S
index 46ebefd0f5..9f3c1079d4 100644
--- a/libavcodec/aarch64/texturedsp_neon.S
+++ b/libavcodec/aarch64/texturedsp_neon.S
@@ -41,6 +41,96 @@ const rgtc1_tab
.short 0, 0, 0, 0, 0, 0, 0, 0
endconst
+const dxt_tab
+ // Bytes of the block holding the two colors, as 16-bit elements
+ .byte 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2,
3, 2, 3
+ // Shifts and masks extracting the components, and what expands them
+ // to 8 bits
+ .short -11, -5, 0, 0, -11, -5, 0, 0
+ .short 31, 63, 31, 0, 31, 63, 31, 0
+ .short 526, 259, 526, 0, 526, 259, 526, 0
+ // 4 colors: reciprocal of the sum of the weights
+ .short 10923, 10923, 10923, 10923, 10923, 10923, 10923, 10923
+ // 3 colors: the same for the third color, and black
+ .short 16384, 16384, 16384, 16384, 0, 0, 0, 0
+endconst
+
+const dxt1_idx_tab
+ // Shifts moving the index of each pixel of a line to bits 2-3
+ .byte 2, 2, 2, 2, 0, 0, 0, 0, -2, -2, -2, -2, -4,
-4, -4, -4
+ // Component of each byte of a line
+ .byte 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0,
1, 2, 3
+endconst
+
+// Computes the 4 colors of the DXT1 block in v0 to v6, with alpha set to 0.
+// There are 2 interpolated colors if the first color is larger than the
+// second one, and one followed by black otherwise.
+// Clobbers x3, v1-v5 and v16-v21.
+.macro dxt_colors
+ movrel x3, dxt_tab
+ ld1 {v16.16b}, [x3], #16
+ ld1 {v17.8h - v20.8h}, [x3], #64
+ ld1 {v21.8h}, [x3]
+ tbl v1.16b, {v0.16b}, v16.16b // c0 x 4, c1 x 4
+ // (t / 32 + t) / 32 with t = 255 * c + 16 is (526 * c + 32) / 64 for
+ // the 32 values of a red or blue component, and so are
+ // (t / 64 + t) / 64 with t = 255 * c + 32 and (259 * c + 32) / 64
+ // for the 64 values of a green one.
+ ushl v3.8h, v1.8h, v17.8h
+ ext v2.16b, v1.16b, v1.16b, #8
+ and v3.16b, v3.16b, v18.16b
+ cmhi v2.8h, v1.8h, v2.8h
+ mul v3.8h, v3.8h, v19.8h
+ dup v2.2d, v2.d[0] // c0 > c1
+ rshrn v6.8b, v3.8h, #6 // r0 g0 b0 0 r1 g1 b1 0
+ bit v21.16b, v20.16b, v2.16b
+ rev64 v4.2s, v6.2s
+ and v5.8b, v6.8b, v2.8b
+ // (2 * c0 + c1) / 3 and (2 * c1 + c0) / 3, or (c0 + c1) / 2 and 0.
+ // The sums are at most 3 * 255, for which the multiplication by
+ // 10923 / 32768 is an exact division by 3.
+ uaddl v3.8h, v6.8b, v4.8b
+ uaddw v3.8h, v3.8h, v5.8b
+ sqdmulh v3.8h, v3.8h, v21.8h
+ xtn v3.8b, v3.8h
+ mov v6.d[1], v3.d[0]
+.endm
+
+// Stores a line of 4 pixels: \idx is the byte of the block holding their
+// indices, \shift what moves each of them to bits 2-3, v23 their mask, \off
+// the offset of each byte within a color, and \tab the 4 colors.
+.macro dxt_line idx, shift, off, tab
+ dup v24.16b, v0.b[\idx]
+ ushl v24.16b, v24.16b, \shift
+ and v24.16b, v24.16b, v23.16b
+ orr v24.16b, v24.16b, \off
+ tbl v24.16b, {\tab}, v24.16b
+.endm
+
+/* int ff_dxt1_block_neon(uint8_t *dst, ptrdiff_t stride,
+ * const uint8_t *block);
+ * x0: dst
+ * x1: stride
+ * x2: block
+ */
+function ff_dxt1_block_neon, export=1
+ ld1 {v0.8b}, [x2]
+ movrel x4, dxt1_idx_tab
+ dxt_colors
+ ld1 {v25.16b, v26.16b}, [x4]
+ movi v7.4s, #0xff, lsl #24
+ movi v23.16b, #0x0c
+ orr v6.16b, v6.16b, v7.16b
+.irp idx, 4, 5, 6
+ dxt_line \idx, v25.16b, v26.16b, v6.16b
+ st1 {v24.16b}, [x0], x1
+.endr
+ dxt_line 7, v25.16b, v26.16b, v6.16b
+ st1 {v24.16b}, [x0]
+ mov w0, #8
+ ret
+endfunc
+
/* int ff_rgtc1u_gray_block_neon(uint8_t *dst, ptrdiff_t stride,
* const uint8_t *block);
* x0: dst
--
2.52.0
>From a025f8bb93cdf9233dce28228adda616c1774db3 Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 18:29:47 -0400
Subject: [PATCH 6/8] avcodec/aarch64/texturedsp: add NEON dxt5_block
This is the block decoder of the DXT5 textures of Hap Alpha, VBN and
DDS.
The alpha values are those of an RGTC1 block, so the code of
rgtc1u_gray_block becomes a macro that both functions use, and the
colors are computed by the code of dxt1_block, without its mode with
three colors. The alpha values are appended to the table of colors, so
that a single lookup gives the four components of the pixels of a line.
On an Apple M4 Pro, with a timer that is coarse for functions this
short:
dxt5_block_c: 10.6
dxt5_block_neon: 1.9 ( 5.49x)
Decoding 240 frames of 3680x2160 VBN DXT5 goes from 200 to 632 frames
per second with one thread (best of 5 runs).
Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
libavcodec/aarch64/texturedsp_init.c | 3 +
libavcodec/aarch64/texturedsp_neon.S | 216 +++++++++++++++++----------
2 files changed, 143 insertions(+), 76 deletions(-)
diff --git a/libavcodec/aarch64/texturedsp_init.c
b/libavcodec/aarch64/texturedsp_init.c
index 3599fac4b7..0d992d4098 100644
--- a/libavcodec/aarch64/texturedsp_init.c
+++ b/libavcodec/aarch64/texturedsp_init.c
@@ -27,6 +27,8 @@
int ff_dxt1_block_neon(uint8_t *dst, ptrdiff_t stride,
const uint8_t *block);
+int ff_dxt5_block_neon(uint8_t *dst, ptrdiff_t stride,
+ const uint8_t *block);
int ff_rgtc1u_gray_block_neon(uint8_t *dst, ptrdiff_t stride,
const uint8_t *block);
@@ -36,6 +38,7 @@ av_cold void ff_texturedsp_init_aarch64(TextureDSPContext *c)
if (have_neon(cpu_flags)) {
c->dxt1_block = ff_dxt1_block_neon;
+ c->dxt5_block = ff_dxt5_block_neon;
c->rgtc1u_gray_block = ff_rgtc1u_gray_block_neon;
}
}
diff --git a/libavcodec/aarch64/texturedsp_neon.S
b/libavcodec/aarch64/texturedsp_neon.S
index 9f3c1079d4..d5ae0b9073 100644
--- a/libavcodec/aarch64/texturedsp_neon.S
+++ b/libavcodec/aarch64/texturedsp_neon.S
@@ -62,82 +62,21 @@ const dxt1_idx_tab
.byte 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0,
1, 2, 3
endconst
-// Computes the 4 colors of the DXT1 block in v0 to v6, with alpha set to 0.
-// There are 2 interpolated colors if the first color is larger than the
-// second one, and one followed by black otherwise.
-// Clobbers x3, v1-v5 and v16-v21.
-.macro dxt_colors
- movrel x3, dxt_tab
- ld1 {v16.16b}, [x3], #16
- ld1 {v17.8h - v20.8h}, [x3], #64
- ld1 {v21.8h}, [x3]
- tbl v1.16b, {v0.16b}, v16.16b // c0 x 4, c1 x 4
- // (t / 32 + t) / 32 with t = 255 * c + 16 is (526 * c + 32) / 64 for
- // the 32 values of a red or blue component, and so are
- // (t / 64 + t) / 64 with t = 255 * c + 32 and (259 * c + 32) / 64
- // for the 64 values of a green one.
- ushl v3.8h, v1.8h, v17.8h
- ext v2.16b, v1.16b, v1.16b, #8
- and v3.16b, v3.16b, v18.16b
- cmhi v2.8h, v1.8h, v2.8h
- mul v3.8h, v3.8h, v19.8h
- dup v2.2d, v2.d[0] // c0 > c1
- rshrn v6.8b, v3.8h, #6 // r0 g0 b0 0 r1 g1 b1 0
- bit v21.16b, v20.16b, v2.16b
- rev64 v4.2s, v6.2s
- and v5.8b, v6.8b, v2.8b
- // (2 * c0 + c1) / 3 and (2 * c1 + c0) / 3, or (c0 + c1) / 2 and 0.
- // The sums are at most 3 * 255, for which the multiplication by
- // 10923 / 32768 is an exact division by 3.
- uaddl v3.8h, v6.8b, v4.8b
- uaddw v3.8h, v3.8h, v5.8b
- sqdmulh v3.8h, v3.8h, v21.8h
- xtn v3.8b, v3.8h
- mov v6.d[1], v3.d[0]
-.endm
+const dxt5_idx_tab
+ // Shifts moving the index of each pixel of a line to bits 2-3, and
+ // out of the byte for the alpha component
+ .byte 2, 2, 2, -8, 0, 0, 0, -8, -2, -2, -2, -8, -4,
-4, -4, -8
+ // Component of each byte of a line, the alpha values following the
+ // 4 colors
+ .byte 0, 1, 2, 16, 0, 1, 2, 17, 0, 1, 2, 18, 0,
1, 2, 19
+ .byte 0, 1, 2, 20, 0, 1, 2, 21, 0, 1, 2, 22, 0,
1, 2, 23
+ .byte 0, 1, 2, 24, 0, 1, 2, 25, 0, 1, 2, 26, 0,
1, 2, 27
+ .byte 0, 1, 2, 28, 0, 1, 2, 29, 0, 1, 2, 30, 0,
1, 2, 31
+endconst
-// Stores a line of 4 pixels: \idx is the byte of the block holding their
-// indices, \shift what moves each of them to bits 2-3, v23 their mask, \off
-// the offset of each byte within a color, and \tab the 4 colors.
-.macro dxt_line idx, shift, off, tab
- dup v24.16b, v0.b[\idx]
- ushl v24.16b, v24.16b, \shift
- and v24.16b, v24.16b, v23.16b
- orr v24.16b, v24.16b, \off
- tbl v24.16b, {\tab}, v24.16b
-.endm
-
-/* int ff_dxt1_block_neon(uint8_t *dst, ptrdiff_t stride,
- * const uint8_t *block);
- * x0: dst
- * x1: stride
- * x2: block
- */
-function ff_dxt1_block_neon, export=1
- ld1 {v0.8b}, [x2]
- movrel x4, dxt1_idx_tab
- dxt_colors
- ld1 {v25.16b, v26.16b}, [x4]
- movi v7.4s, #0xff, lsl #24
- movi v23.16b, #0x0c
- orr v6.16b, v6.16b, v7.16b
-.irp idx, 4, 5, 6
- dxt_line \idx, v25.16b, v26.16b, v6.16b
- st1 {v24.16b}, [x0], x1
-.endr
- dxt_line 7, v25.16b, v26.16b, v6.16b
- st1 {v24.16b}, [x0]
- mov w0, #8
- ret
-endfunc
-
-/* int ff_rgtc1u_gray_block_neon(uint8_t *dst, ptrdiff_t stride,
- * const uint8_t *block);
- * x0: dst
- * x1: stride
- * x2: block
- */
-function ff_rgtc1u_gray_block_neon, export=1
+// Decodes the 16 values of the RGTC1 block at x2 to \dst.
+// Clobbers x3-x5, v0-v7 and v16-v23.
+.macro rgtc1_values dst
ldrb w3, [x2]
ldrb w5, [x2, #1]
movrel x4, rgtc1_tab
@@ -168,7 +107,132 @@ function ff_rgtc1u_gray_block_neon, export=1
sqdmulh v4.8h, v4.8h, v22.8h
and v2.16b, v2.16b, v7.16b // 2 * index
orr v4.16b, v4.16b, v23.16b // values
- tbl v0.16b, {v4.16b}, v2.16b
+ tbl \dst, {v4.16b}, v2.16b
+.endm
+
+// Computes the 4 colors of the DXT1 block in v0 to v6, with alpha set to 0.
+// DXT1 has 2 interpolated colors if the first color is larger than the
+// second one, and one followed by black otherwise; the other formats always
+// have 2. Clobbers x3, v1-v5 and v16-v21.
+.macro dxt_colors dxt1
+ movrel x3, dxt_tab
+ ld1 {v16.16b}, [x3], #16
+ ld1 {v17.8h - v20.8h}, [x3], #64
+.if \dxt1
+ ld1 {v21.8h}, [x3]
+.endif
+ tbl v1.16b, {v0.16b}, v16.16b // c0 x 4, c1 x 4
+ // (t / 32 + t) / 32 with t = 255 * c + 16 is (526 * c + 32) / 64 for
+ // the 32 values of a red or blue component, and so are
+ // (t / 64 + t) / 64 with t = 255 * c + 32 and (259 * c + 32) / 64
+ // for the 64 values of a green one.
+ ushl v3.8h, v1.8h, v17.8h
+.if \dxt1
+ ext v2.16b, v1.16b, v1.16b, #8
+.endif
+ and v3.16b, v3.16b, v18.16b
+.if \dxt1
+ cmhi v2.8h, v1.8h, v2.8h
+.endif
+ mul v3.8h, v3.8h, v19.8h
+.if \dxt1
+ dup v2.2d, v2.d[0] // c0 > c1
+.endif
+ rshrn v6.8b, v3.8h, #6 // r0 g0 b0 0 r1 g1 b1 0
+.if \dxt1
+ bit v21.16b, v20.16b, v2.16b
+.endif
+ rev64 v4.2s, v6.2s
+.if \dxt1
+ and v5.8b, v6.8b, v2.8b
+.endif
+ // (2 * c0 + c1) / 3 and (2 * c1 + c0) / 3, or (c0 + c1) / 2 and 0.
+ // The sums are at most 3 * 255, for which the multiplication by
+ // 10923 / 32768 is an exact division by 3.
+ uaddl v3.8h, v6.8b, v4.8b
+.if \dxt1
+ uaddw v3.8h, v3.8h, v5.8b
+ sqdmulh v3.8h, v3.8h, v21.8h
+.else
+ uaddw v3.8h, v3.8h, v6.8b
+ sqdmulh v3.8h, v3.8h, v20.8h
+.endif
+ xtn v3.8b, v3.8h
+ mov v6.d[1], v3.d[0]
+.endm
+
+// Stores a line of 4 pixels: \idx is the byte of the block holding their
+// indices, \shift what moves each of them to bits 2-3, v23 their mask, \off
+// the offset of each byte within a color, and \tab the 4 colors.
+.macro dxt_line idx, shift, off, tab:vararg
+ dup v24.16b, v0.b[\idx]
+ ushl v24.16b, v24.16b, \shift
+ and v24.16b, v24.16b, v23.16b
+ orr v24.16b, v24.16b, \off
+ tbl v24.16b, {\tab}, v24.16b
+.endm
+
+/* int ff_dxt1_block_neon(uint8_t *dst, ptrdiff_t stride,
+ * const uint8_t *block);
+ * x0: dst
+ * x1: stride
+ * x2: block
+ */
+function ff_dxt1_block_neon, export=1
+ ld1 {v0.8b}, [x2]
+ movrel x4, dxt1_idx_tab
+ dxt_colors 1
+ ld1 {v25.16b, v26.16b}, [x4]
+ movi v7.4s, #0xff, lsl #24
+ movi v23.16b, #0x0c
+ orr v6.16b, v6.16b, v7.16b
+.irp idx, 4, 5, 6
+ dxt_line \idx, v25.16b, v26.16b, v6.16b
+ st1 {v24.16b}, [x0], x1
+.endr
+ dxt_line 7, v25.16b, v26.16b, v6.16b
+ st1 {v24.16b}, [x0]
+ mov w0, #8
+ ret
+endfunc
+
+/* int ff_dxt5_block_neon(uint8_t *dst, ptrdiff_t stride,
+ * const uint8_t *block);
+ * x0: dst
+ * x1: stride
+ * x2: block
+ */
+function ff_dxt5_block_neon, export=1
+ rgtc1_values v7.16b
+ add x2, x2, #8
+ movrel x4, dxt5_idx_tab
+ ld1 {v0.8b}, [x2]
+ dxt_colors 0
+ ld1 {v22.16b}, [x4], #16
+ ld1 {v25.16b - v28.16b}, [x4]
+ movi v23.16b, #0x0c
+ // The alpha values follow the colors in the table, and are selected
+ // by constant indices.
+ dxt_line 4, v22.16b, v25.16b, v6.16b, v7.16b
+ st1 {v24.16b}, [x0], x1
+ dxt_line 5, v22.16b, v26.16b, v6.16b, v7.16b
+ st1 {v24.16b}, [x0], x1
+ dxt_line 6, v22.16b, v27.16b, v6.16b, v7.16b
+ st1 {v24.16b}, [x0], x1
+ dxt_line 7, v22.16b, v28.16b, v6.16b, v7.16b
+ st1 {v24.16b}, [x0]
+ mov w0, #16
+ ret
+endfunc
+
+/* int ff_rgtc1u_gray_block_neon(uint8_t *dst, ptrdiff_t stride,
+ * const uint8_t *block);
+ * x0: dst
+ * x1: stride
+ * x2: block
+ */
+function ff_rgtc1u_gray_block_neon, export=1
+ rgtc1_values v0.16b
st1 {v0.s}[0], [x0], x1
st1 {v0.s}[1], [x0], x1
st1 {v0.s}[2], [x0], x1
--
2.52.0
>From a7b8091138f5ded6415db077007921486ff1c745 Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 20:27:56 -0400
Subject: [PATCH 7/8] avcodec/x86/texturedsp: add SSSE3 dxt1_block
This is the block decoder of the DXT1 format of DXV, Hap and VBN. It
follows the NEON version.
The components of the two colors are moved to the top of 16-bit
elements by a multiplication, and expanded to 8 bits with pmulhrsw,
which gives what the two divisions of the C version do for all of
their values. The interpolated colors are computed for the two colors
at once, with a reciprocal that is exact for the sums involved, and the
mode is chosen without a branch by selecting the weights with cmov.
As there is no shift of bytes by variable amounts, the 16 indices are
obtained by masking copies of the bytes holding them, shifted right by
4 for half of them, and mapping the 7 values this leaves to the offset
of a color with pshufb. The pixels of a line are then looked up in the
table of the four colors.
It is limited to x86-64, like the function already in this file. On an
Intel Core i9-11900K:
dxt1_block_c: 49.2
dxt1_block_ssse3: 7.7 ( 6.36x)
Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
libavcodec/x86/texturedsp.asm | 111 +++++++++++++++++++++++++++++++
libavcodec/x86/texturedsp_init.c | 6 +-
2 files changed, 116 insertions(+), 1 deletion(-)
diff --git a/libavcodec/x86/texturedsp.asm b/libavcodec/x86/texturedsp.asm
index d5c5000ea9..b5e6560290 100644
--- a/libavcodec/x86/texturedsp.asm
+++ b/libavcodec/x86/texturedsp.asm
@@ -43,8 +43,119 @@ rgtc1_interp4: db 5, 0, 0, 5, 4, 1, 3, 2, 2, 3, 1, 4, 0, 0,
0, 0
times 8 dw 13108
dw 0, 0, 0, 0, 0, 0, 0, 255
+; Bytes of the block holding the two colors, as 16-bit elements
+dxt_colors: db 0, 1, 0, 1, 0, 1, 0, 1, 2, 3, 2, 3, 2, 3, 2, 3
+; Multipliers moving each component to the highest bits, the masks of the
+; components once shifted right by 1, and what expands them to 8 bits
+dxt_comp_mul: dw 1, 32, 2048, 0, 1, 32, 2048, 0
+dxt_comp_mask: dw 0x7c00, 0x7e00, 0x7c00, 0, 0x7c00, 0x7e00, 0x7c00, 0
+dxt_comp_exp: dw 263, 259, 263, 0, 263, 259, 263, 0
+; 2 interpolated colors: mask of the color that is added twice, and
+; reciprocal of the sum of the weights
+dxt_interp4: times 8 dw 0xffff
+ times 8 dw 21846
+; 1 interpolated color and black
+dxt_interp3: times 8 dw 0
+ dw 32768, 32768, 32768, 32768, 0, 0, 0, 0
+; The byte holding the index of each pixel, and the same byte shifted right
+; by 4 for the last two pixels of a line; the bits of the index in it; and
+; the offset of the color for each value of these bits
+dxt_idx: db 4, 4, 12, 12, 5, 5, 13, 13, 6, 6, 14, 14, 7, 7, 15, 15
+dxt_idx_mask: times 4 db 3, 12, 3, 12
+dxt_idx_lut: db 0, 4, 8, 12, 4, 0, 0, 0, 8, 0, 0, 0, 12, 0, 0, 0
+; Component of each byte of a line
+dxt_comp: times 4 db 0, 1, 2, 3
+dxt_alpha: times 4 dd 0xff000000
+
SECTION .text
+;------------------------------------------------------------------------------
+; Computes the 4 colors of the DXT1 block at blockq + %1 to m1, with alpha set
+; to 0, and 4 times the index of each of the 16 pixels to m4. DXT1 (%2) has 2
+; interpolated colors if the first color is larger than the second one, and
+; one followed by black otherwise; the other formats always have 2.
+; Clobbers m0, m2, m3, tabq and tmpq.
+;------------------------------------------------------------------------------
+%macro DXT_COLORS 2
+ movq m0, [blockq+%1]
+%if %2
+ movzx tmpd, word [blockq+%1]
+ lea tabq, [dxt_interp3]
+ cmp tmpw, [blockq+%1+2]
+ lea tmpq, [dxt_interp4]
+ cmova tabq, tmpq
+%endif
+ ; (t / 32 + t) / 32 with t = 255 * c + 16 is (263 * c + 16) / 32 for the
+ ; 32 values of a red or blue component, and so are (t / 64 + t) / 64 with
+ ; t = 255 * c + 32 and (259 * c + 32) / 64 for the 64 values of a green
+ ; one, which is what pmulhrsw computes from c << 10 and c << 9.
+ pshufb m1, m0, [dxt_colors] ; c0 x 4, c1 x 4
+ psrlq m4, m0, 4
+ pmullw m1, [dxt_comp_mul]
+ punpcklqdq m0, m4
+ psrlw m1, 1
+ pshufb m0, [dxt_idx]
+ pand m1, [dxt_comp_mask]
+ pand m0, [dxt_idx_mask]
+ pmulhrsw m1, [dxt_comp_exp] ; r0 g0 b0 0 r1 g1 b1 0
+ mova m4, [dxt_idx_lut]
+ pshufd m2, m1, q1032
+ pshufb m4, m0
+ ; (2 * c0 + c1) / 3 and (2 * c1 + c0) / 3, or (c0 + c1) / 2 and 0.
+ ; The sums are at most 3 * 255, for which the multiplication by
+ ; 21846 / 65536 is an exact division by 3.
+%if %2
+ pand m3, m1, [tabq]
+ paddw m2, m1
+ paddw m3, m2
+ pmulhuw m3, [tabq+16]
+%else
+ paddw m3, m1, m1
+ paddw m3, m2
+ pmulhuw m3, [dxt_interp4+16]
+%endif
+ packuswb m1, m3
+%endmacro
+
+;------------------------------------------------------------------------------
+; Replicates the bytes of m4, 4 times the index of each pixel, over the 4
+; bytes of their pixel, and adds the offset of each component: the result is
+; in m2, m4, m3 and m5 for the 4 lines. Clobbers m0.
+;------------------------------------------------------------------------------
+%macro DXT_INDICES 0
+ punpckhbw m5, m4, m4
+ punpcklbw m4, m4
+ mova m0, [dxt_comp]
+ punpcklwd m2, m4, m4
+ punpckhwd m4, m4
+ punpcklwd m3, m5, m5
+ punpckhwd m5, m5
+ por m2, m0
+ por m4, m0
+ por m3, m0
+ por m5, m0
+%endmacro
+
+;------------------------------------------------------------------------------
+; int ff_dxt1_block(uint8_t *dst, ptrdiff_t stride, const uint8_t *block)
+;------------------------------------------------------------------------------
+INIT_XMM ssse3
+cglobal dxt1_block, 3, 5, 7, dst, stride, block, tab, tmp
+ DXT_COLORS 0, 1
+ por m1, [dxt_alpha]
+ DXT_INDICES
+ lea tmpq, [strideq*3]
+ pshufb m6, m1, m2
+ pshufb m2, m1, m4
+ pshufb m0, m1, m3
+ pshufb m1, m5
+ movu [dstq], m6
+ movu [dstq+strideq], m2
+ movu [dstq+strideq*2], m0
+ movu [dstq+tmpq], m1
+ mov eax, 8
+ RET
+
;------------------------------------------------------------------------------
; int ff_rgtc1u_gray_block(uint8_t *dst, ptrdiff_t stride, const uint8_t
*block)
;------------------------------------------------------------------------------
diff --git a/libavcodec/x86/texturedsp_init.c b/libavcodec/x86/texturedsp_init.c
index 76e70dfab4..72c341d06e 100644
--- a/libavcodec/x86/texturedsp_init.c
+++ b/libavcodec/x86/texturedsp_init.c
@@ -27,6 +27,8 @@
#include "libavutil/x86/cpu.h"
#include "libavcodec/texturedsp.h"
+int ff_dxt1_block_ssse3(uint8_t *dst, ptrdiff_t stride,
+ const uint8_t *block);
int ff_rgtc1u_gray_block_ssse3(uint8_t *dst, ptrdiff_t stride,
const uint8_t *block);
@@ -35,7 +37,9 @@ av_cold void ff_texturedsp_init_x86(TextureDSPContext *c)
#if ARCH_X86_64
int cpu_flags = av_get_cpu_flags();
- if (EXTERNAL_SSSE3(cpu_flags))
+ if (EXTERNAL_SSSE3(cpu_flags)) {
+ c->dxt1_block = ff_dxt1_block_ssse3;
c->rgtc1u_gray_block = ff_rgtc1u_gray_block_ssse3;
+ }
#endif
}
--
2.52.0
>From b0735088ec8f505faf44b49b6d602fe064ad4fe7 Mon Sep 17 00:00:00 2001
From: Graham Vosloo <[email protected]>
Date: Sun, 11 Oct 2026 20:27:56 -0400
Subject: [PATCH 8/8] avcodec/x86/texturedsp: add SSSE3 dxt5_block
This is the block decoder of the DXT5 textures of Hap Alpha, VBN and
DDS. It follows the NEON version.
The alpha values of a DXT5 block are an RGTC1 block, so the body of
rgtc1u_gray_block becomes a macro that both functions use, and the
colors are decoded like those of DXT1, without its mode with three
colors. The 16 alpha values are then moved to the highest byte of
their pixel and merged with the colors of each line.
As for the other functions of this file, it is limited to x86-64. On
an Intel Core i9-11900K:
dxt5_block_c: 148.9
dxt5_block_ssse3: 14.7 (10.04x)
Assisted-by: Claude Opus 5.5
Signed-off-by: Graham Vosloo <[email protected]>
---
libavcodec/x86/texturedsp.asm | 91 +++++++++++++++++++++++---------
libavcodec/x86/texturedsp_init.c | 3 ++
2 files changed, 69 insertions(+), 25 deletions(-)
diff --git a/libavcodec/x86/texturedsp.asm b/libavcodec/x86/texturedsp.asm
index b5e6560290..c6712e9fb0 100644
--- a/libavcodec/x86/texturedsp.asm
+++ b/libavcodec/x86/texturedsp.asm
@@ -69,6 +69,38 @@ dxt_alpha: times 4 dd 0xff000000
SECTION .text
+;------------------------------------------------------------------------------
+; Decodes the 16 values of the RGTC1 block at blockq to m1.
+; Clobbers m0, m2, tabq and tmpq.
+;------------------------------------------------------------------------------
+%macro RGTC1_VALUES 0
+ movzx tmpd, byte [blockq]
+ movq m0, [blockq]
+ lea tabq, [rgtc1_interp4]
+ cmp tmpb, [blockq+1]
+ lea tmpq, [rgtc1_interp6]
+ ; There are 6 interpolated values if the first reference value is larger
+ ; than the second one, and 4 otherwise.
+ cmova tabq, tmpq
+
+ ; The 8 values are computed as 16-bit elements: the weighted sum of the
+ ; reference values is at most 7 * 255, for which the multiplications by
+ ; 9363 / 65536 and 13108 / 65536 are exact divisions by 7 and 5.
+ pshufb m1, m0, [rgtc1_refs]
+ pshufb m2, m0, [rgtc1_idx_lo]
+ pshufb m0, [rgtc1_idx_hi]
+ pmaddubsw m1, [tabq]
+ pmullw m2, [rgtc1_idx_mul]
+ pmullw m0, [rgtc1_idx_mul]
+ pmulhuw m1, [tabq+16]
+ psrlw m2, 13
+ psrlw m0, 13
+ por m1, [tabq+32]
+ packuswb m2, m0 ; indices
+ packuswb m1, m1 ; values
+ pshufb m1, m2
+%endmacro
+
;------------------------------------------------------------------------------
; Computes the 4 colors of the DXT1 block at blockq + %1 to m1, with alpha set
; to 0, and 4 times the index of each of the 16 pixels to m4. DXT1 (%2) has 2
@@ -156,37 +188,46 @@ cglobal dxt1_block, 3, 5, 7, dst, stride, block, tab, tmp
mov eax, 8
RET
+;------------------------------------------------------------------------------
+; int ff_dxt5_block(uint8_t *dst, ptrdiff_t stride, const uint8_t *block)
+;------------------------------------------------------------------------------
+INIT_XMM ssse3
+cglobal dxt5_block, 3, 5, 12, dst, stride, block, tab, tmp
+ RGTC1_VALUES
+ mova m7, m1
+ DXT_COLORS 8, 0
+ ; The alpha values are moved to the highest byte of their pixel.
+ pxor m0, m0
+ punpcklbw m6, m0, m7
+ punpckhbw m8, m0, m7
+ punpcklwd m9, m0, m6
+ punpckhwd m10, m0, m6
+ punpcklwd m11, m0, m8
+ punpckhwd m7, m0, m8
+ DXT_INDICES
+ lea tmpq, [strideq*3]
+ pshufb m6, m1, m2
+ pshufb m2, m1, m4
+ pshufb m0, m1, m3
+ pshufb m1, m5
+ por m6, m9
+ por m2, m10
+ por m0, m11
+ por m1, m7
+ movu [dstq], m6
+ movu [dstq+strideq], m2
+ movu [dstq+strideq*2], m0
+ movu [dstq+tmpq], m1
+ mov eax, 16
+ RET
+
;------------------------------------------------------------------------------
; int ff_rgtc1u_gray_block(uint8_t *dst, ptrdiff_t stride, const uint8_t
*block)
;------------------------------------------------------------------------------
INIT_XMM ssse3
cglobal rgtc1u_gray_block, 3, 5, 3, dst, stride, block, tab, tmp
- movzx tmpd, byte [blockq]
- movq m0, [blockq]
- lea tabq, [rgtc1_interp4]
- cmp tmpb, [blockq+1]
- lea tmpq, [rgtc1_interp6]
- ; There are 6 interpolated values if the first reference value is larger
- ; than the second one, and 4 otherwise.
- cmova tabq, tmpq
-
- ; The 8 values are computed as 16-bit elements: the weighted sum of the
- ; reference values is at most 7 * 255, for which the multiplications by
- ; 9363 / 65536 and 13108 / 65536 are exact divisions by 7 and 5.
- pshufb m1, m0, [rgtc1_refs]
- pshufb m2, m0, [rgtc1_idx_lo]
- pshufb m0, [rgtc1_idx_hi]
- pmaddubsw m1, [tabq]
- pmullw m2, [rgtc1_idx_mul]
- pmullw m0, [rgtc1_idx_mul]
- pmulhuw m1, [tabq+16]
- psrlw m2, 13
- psrlw m0, 13
- por m1, [tabq+32]
- packuswb m2, m0 ; indices
- packuswb m1, m1 ; values
+ RGTC1_VALUES
lea tmpq, [strideq*3]
- pshufb m1, m2
movd [dstq], m1
psrldq m1, 4
movd [dstq+strideq], m1
diff --git a/libavcodec/x86/texturedsp_init.c b/libavcodec/x86/texturedsp_init.c
index 72c341d06e..63c1b08afb 100644
--- a/libavcodec/x86/texturedsp_init.c
+++ b/libavcodec/x86/texturedsp_init.c
@@ -29,6 +29,8 @@
int ff_dxt1_block_ssse3(uint8_t *dst, ptrdiff_t stride,
const uint8_t *block);
+int ff_dxt5_block_ssse3(uint8_t *dst, ptrdiff_t stride,
+ const uint8_t *block);
int ff_rgtc1u_gray_block_ssse3(uint8_t *dst, ptrdiff_t stride,
const uint8_t *block);
@@ -39,6 +41,7 @@ av_cold void ff_texturedsp_init_x86(TextureDSPContext *c)
if (EXTERNAL_SSSE3(cpu_flags)) {
c->dxt1_block = ff_dxt1_block_ssse3;
+ c->dxt5_block = ff_dxt5_block_ssse3;
c->rgtc1u_gray_block = ff_rgtc1u_gray_block_ssse3;
}
#endif
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]