From 077a3eb2bf74db10282d427175282bcc1462f8d6 Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <barracuda@macos-powerpc.org>
Date: Sat, 15 Aug 2026 20:35:37 +0000
Subject: [PATCH 33/38] avcodec/ppc: hpeldsp x2/y2/xy2 half-pel kernels, from
 PowerVLC

Upstream's AltiVec hpeldsp only ever covered the [0][0]/[1][0]/xy2
positions; the x2/y2 positions ran scalar C, and DVD MPEG-2 playback on
a G4 profiles them as a significant cost.  Port the missing kernels from
PowerVLC (github.com/Olsro/powervlc, contrib/src/ffmpeg,
ffmpeg-ppc-hpeldsp-altivec.patch), who validated them bit-exact against
the C reference with 55872 randomized tests and benched them on a real
1.42 GHz 7447A.

Added and registered on both CPUs (16-wide; our G5 benches show 16-wide
hpel work strongly pro-vector -- the plain copies win 2.96-3.90x -- and
these do strictly more arithmetic per memory access):
  put_pixels16_x2, put_pixels16_y2, avg_pixels16_x2, avg_pixels16_y2,
  avg_pixels16_xy2

Added and registered on the G4 only (#ifndef _ARCH_PWR4: no 970 data
yet, and the 970's record on 8-wide hpel work is poor):
  put_pixels8_x2 (lvsr + two stvewx, no read-modify-write of the
  neighbouring 8 bytes), avg_pixels8_x2 (a 0.99x wash on the 7447A,
  kept there as PowerVLC keeps it), avg_pixels8_y2

Deliberately NOT added, matching PowerVLC's own 7447A findings:
put_pixels8 (plain copy) and put_pixels8_y2 stay scalar -- measured
faster in C there, and our own 970 data agrees for the plain copy, whose
registration is now dropped everywhere (0.42x on the 970; PowerVLC's
cheaper-store variant of the same copy still loses at 0.75x on the
7447A, so our RMW-splice version can only do worse).

Validated: checkasm hpeldsp under the qemu -cpu 7400 harness exercises
every size and every dxy position bit-exact against C.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRRFPPzH3qXUcq8Ask5LZh
---
 libavcodec/ppc/hpeldsp_altivec.c | 240 ++++++++++++++++++++++++++++++-
 1 file changed, 238 insertions(+), 2 deletions(-)

diff --git a/libavcodec/ppc/hpeldsp_altivec.c b/libavcodec/ppc/hpeldsp_altivec.c
index 0b79d08..3aeb6a0 100644
--- a/libavcodec/ppc/hpeldsp_altivec.c
+++ b/libavcodec/ppc/hpeldsp_altivec.c
@@ -388,6 +388,226 @@ static void avg_pixels8_xy2_altivec(uint8_t *block, const uint8_t *pixels, ptrdi
         pixels += line_size;
     }
 }
+
+/* The x2/y2 half-pel variants below are from PowerVLC
+ * (github.com/Olsro/powervlc, contrib/src/ffmpeg): upstream only ever
+ * vectorised the [0][0]/[1][0]/xy2 positions, and DVD MPEG-2 playback on a
+ * 7447A profiles the scalar C fallbacks of the remaining positions as a
+ * significant cost.  Their harness validated all of them bit-exact against
+ * the C reference (55872 randomized tests). */
+
+/* next one assumes that ((line_size % 16) == 0) */
+static void put_pixels16_x2_altivec(uint8_t *block, const uint8_t *pixels, ptrdiff_t line_size, int h)
+{
+    register int i;
+
+    for (i = 0; i < h; i++) {
+        register vector unsigned char first  = vec_ld(0,  pixels);
+        register vector unsigned char second = vec_ld(16, pixels);
+        register vector unsigned char perm   = vec_lvsl(0, pixels);
+        register vector unsigned char pixelsv1 = vec_perm(first, second, perm);
+        register vector unsigned char pixelsv2 = vec_perm(first, second, vec_add(perm, vec_splat_u8(1)));
+
+        vec_st(vec_avg(pixelsv1, pixelsv2), 0, block);
+
+        pixels += line_size;
+        block  += line_size;
+    }
+}
+
+/* next one assumes that ((line_size % 16) == 0) */
+static void put_pixels16_y2_altivec(uint8_t *block, const uint8_t *pixels, ptrdiff_t line_size, int h)
+{
+    register int i;
+    register vector unsigned char pixelsv1 = unaligned_load(0, pixels);
+
+    for (i = 0; i < h; i++) {
+        register vector unsigned char pixelsv2 = unaligned_load(line_size, pixels);
+
+        vec_st(vec_avg(pixelsv1, pixelsv2), 0, block);
+
+        pixelsv1 = pixelsv2;
+        pixels += line_size;
+        block  += line_size;
+    }
+}
+
+/* next one assumes that ((line_size % 16) == 0) */
+static void avg_pixels16_x2_altivec(uint8_t *block, const uint8_t *pixels, ptrdiff_t line_size, int h)
+{
+    register int i;
+
+    for (i = 0; i < h; i++) {
+        register vector unsigned char first  = vec_ld(0,  pixels);
+        register vector unsigned char second = vec_ld(16, pixels);
+        register vector unsigned char perm   = vec_lvsl(0, pixels);
+        register vector unsigned char pixelsv1 = vec_perm(first, second, perm);
+        register vector unsigned char pixelsv2 = vec_perm(first, second, vec_add(perm, vec_splat_u8(1)));
+        register vector unsigned char blockv   = vec_ld(0, block);
+
+        vec_st(vec_avg(blockv, vec_avg(pixelsv1, pixelsv2)), 0, block);
+
+        pixels += line_size;
+        block  += line_size;
+    }
+}
+
+/* next one assumes that ((line_size % 16) == 0) */
+static void avg_pixels16_y2_altivec(uint8_t *block, const uint8_t *pixels, ptrdiff_t line_size, int h)
+{
+    register int i;
+    register vector unsigned char pixelsv1 = unaligned_load(0, pixels);
+
+    for (i = 0; i < h; i++) {
+        register vector unsigned char pixelsv2 = unaligned_load(line_size, pixels);
+        register vector unsigned char blockv   = vec_ld(0, block);
+
+        vec_st(vec_avg(blockv, vec_avg(pixelsv1, pixelsv2)), 0, block);
+
+        pixelsv1 = pixelsv2;
+        pixels += line_size;
+        block  += line_size;
+    }
+}
+
+/* next one assumes that ((line_size % 16) == 0) */
+static void avg_pixels16_xy2_altivec(uint8_t *block, const uint8_t *pixels, ptrdiff_t line_size, int h)
+{
+    register int i;
+    register vector unsigned char pixelsv1, pixelsv2, pixelsv3, pixelsv4;
+    register vector unsigned char blockv;
+    register vector unsigned short temp3, temp4,
+        pixelssum1, pixelssum2, pixelssum3, pixelssum4;
+    register const vector unsigned char vczero = (const vector unsigned char)vec_splat_u8(0);
+    register const vector unsigned short vctwo = (const vector unsigned short)vec_splat_u16(2);
+
+    pixelsv1 = VEC_LD(0, pixels);
+    pixelsv2 = VEC_LD(1, pixels);
+    pixelsv3 = VEC_MERGEL(vczero, pixelsv1);
+    pixelsv4 = VEC_MERGEL(vczero, pixelsv2);
+    pixelsv1 = VEC_MERGEH(vczero, pixelsv1);
+    pixelsv2 = VEC_MERGEH(vczero, pixelsv2);
+    pixelssum3 = vec_add((vector unsigned short)pixelsv3,
+                         (vector unsigned short)pixelsv4);
+    pixelssum3 = vec_add(pixelssum3, vctwo);
+    pixelssum1 = vec_add((vector unsigned short)pixelsv1,
+                         (vector unsigned short)pixelsv2);
+    pixelssum1 = vec_add(pixelssum1, vctwo);
+
+    for (i = 0; i < h ; i++) {
+        blockv = vec_ld(0, block);
+
+        pixelsv1 = unaligned_load(line_size, pixels);
+        pixelsv2 = unaligned_load(line_size+1, pixels);
+
+        pixelsv3 = VEC_MERGEL(vczero, pixelsv1);
+        pixelsv4 = VEC_MERGEL(vczero, pixelsv2);
+        pixelsv1 = VEC_MERGEH(vczero, pixelsv1);
+        pixelsv2 = VEC_MERGEH(vczero, pixelsv2);
+        pixelssum4 = vec_add((vector unsigned short)pixelsv3,
+                             (vector unsigned short)pixelsv4);
+        pixelssum2 = vec_add((vector unsigned short)pixelsv1,
+                             (vector unsigned short)pixelsv2);
+        temp4 = vec_add(pixelssum3, pixelssum4);
+        temp4 = vec_sra(temp4, vctwo);
+        temp3 = vec_add(pixelssum1, pixelssum2);
+        temp3 = vec_sra(temp3, vctwo);
+
+        pixelssum3 = vec_add(pixelssum4, vctwo);
+        pixelssum1 = vec_add(pixelssum2, vctwo);
+
+        blockv = vec_avg(blockv, vec_packsu(temp3, temp4));
+
+        vec_st(blockv, 0, block);
+
+        block += line_size;
+        pixels += line_size;
+    }
+}
+
+#ifndef _ARCH_PWR4
+/* 8-wide variants, registered on the G4 only: no 970 bench data yet, and
+ * the 970's record on 8-wide hpel work is poor (the plain 8-wide copies
+ * lose there).  PowerVLC's 7447A numbers: put_pixels8_x2 wins,
+ * avg_pixels8_y2 wins, avg_pixels8_x2 a 0.99x wash (kept for the G4 as
+ * they kept it). */
+
+/* next one assumes that ((line_size % 8) == 0);
+ * block is written with two 32-bit vec_ste (no read-modify-write of the
+ * neighbouring 8 bytes, no dst load) */
+static void put_pixels8_x2_altivec(uint8_t *block, const uint8_t *pixels, ptrdiff_t line_size, int h)
+{
+    register int i;
+
+    for (i = 0; i < h; i++) {
+        register vector unsigned char pixelsv1  = unaligned_load(0, pixels);
+        register vector unsigned char pixelsavg = vec_avg(pixelsv1, vec_sld(pixelsv1, pixelsv1, 1));
+        /* rotate the 8 result bytes to the 8- or 16-byte alignment of
+         * block, then store them as two word elements */
+        register vector unsigned char blockv   = vec_perm(pixelsavg, pixelsavg, vec_lvsr(0, block));
+
+        vec_ste((vector unsigned int)blockv, 0, (unsigned int *)block);
+        vec_ste((vector unsigned int)blockv, 4, (unsigned int *)block);
+
+        pixels += line_size;
+        block  += line_size;
+    }
+}
+
+/* next one assumes that ((line_size % 8) == 0) */
+static void avg_pixels8_x2_altivec(uint8_t *block, const uint8_t *pixels, ptrdiff_t line_size, int h)
+{
+    register int i;
+
+    for (i = 0; i < h; i++) {
+        int rightside = ((unsigned long)block & 0x0000000F);
+        register vector unsigned char blockv    = vec_ld(0, block);
+        register vector unsigned char pixelsv1  = unaligned_load(0, pixels);
+        register vector unsigned char pixelsavg = vec_avg(pixelsv1, vec_sld(pixelsv1, pixelsv1, 1));
+        register vector unsigned char blocktemp;
+
+        if (rightside) {
+            blocktemp = vec_perm(blockv, pixelsavg, vcprm(0, 1, s0, s1));
+        } else {
+            blocktemp = vec_perm(blockv, pixelsavg, vcprm(s0, s1, 2, 3));
+        }
+
+        blockv = vec_avg(blocktemp, blockv);
+        vec_st(blockv, 0, block);
+
+        pixels += line_size;
+        block  += line_size;
+    }
+}
+
+/* next one assumes that ((line_size % 8) == 0) */
+static void avg_pixels8_y2_altivec(uint8_t *block, const uint8_t *pixels, ptrdiff_t line_size, int h)
+{
+    register int i;
+    register vector unsigned char pixelsv1 = unaligned_load(0, pixels);
+
+    for (i = 0; i < h; i++) {
+        int rightside = ((unsigned long)block & 0x0000000F);
+        register vector unsigned char blockv    = vec_ld(0, block);
+        register vector unsigned char pixelsv2  = unaligned_load(line_size, pixels);
+        register vector unsigned char pixelsavg = vec_avg(pixelsv1, pixelsv2);
+        register vector unsigned char blocktemp;
+
+        if (rightside) {
+            blocktemp = vec_perm(blockv, pixelsavg, vcprm(0, 1, s0, s1));
+        } else {
+            blocktemp = vec_perm(blockv, pixelsavg, vcprm(s0, s1, 2, 3));
+        }
+
+        blockv = vec_avg(blocktemp, blockv);
+        vec_st(blockv, 0, block);
+
+        pixelsv1 = pixelsv2;
+        pixels += line_size;
+        block  += line_size;
+    }
+}
+#endif /* !_ARCH_PWR4 */
 #endif /* HAVE_ALTIVEC */
 
 av_cold void ff_hpeldsp_init_ppc(HpelDSPContext *c, int flags)
@@ -397,15 +617,31 @@ av_cold void ff_hpeldsp_init_ppc(HpelDSPContext *c, int flags)
         return;
 
     c->avg_pixels_tab[0][0]        = ff_avg_pixels16_altivec;
+    c->avg_pixels_tab[0][1]        = avg_pixels16_x2_altivec;
+    c->avg_pixels_tab[0][2]        = avg_pixels16_y2_altivec;
+    c->avg_pixels_tab[0][3]        = avg_pixels16_xy2_altivec;
     /* ff_avg_pixels8_altivec deliberately not registered: loses to C on
      * both target CPUs (7447A 0.80x; 970 0.51-0.58x on the 2026-07-30
      * re-bench, two independent entries — an earlier 2.46x reading did
      * not reproduce).  The kernel is kept for other CPUs. */
+#ifndef _ARCH_PWR4 /* 8-wide x2/y2: G4 only, no 970 data yet (see above) */
+    c->avg_pixels_tab[1][1]        = avg_pixels8_x2_altivec;
+    c->avg_pixels_tab[1][2]        = avg_pixels8_y2_altivec;
+#endif
     c->avg_pixels_tab[1][3]        = avg_pixels8_xy2_altivec;
 
     c->put_pixels_tab[0][0]        = ff_put_pixels16_altivec;
-#ifndef _ARCH_PWR4 /* 970: 0.42x vs C (scalar 8x8 copy wins) */
-    c->put_pixels_tab[1][0]        = ff_put_pixels8_altivec;
+    /* ff_put_pixels8_altivec deliberately not registered: 0.42x vs C on the
+     * 970, and the 8-wide plain copy also loses on a 7447A — PowerVLC
+     * (github.com/Olsro/powervlc) measured 0.75x there (2026-08-07) for
+     * their own variant of the same copy, which uses a cheaper
+     * lvsr+stvewx store than our read-modify-write splice, so ours can
+     * only do worse.  A loss on both target CPUs; kernel kept for other
+     * callers/CPUs. */
+    c->put_pixels_tab[0][1]        = put_pixels16_x2_altivec;
+    c->put_pixels_tab[0][2]        = put_pixels16_y2_altivec;
+#ifndef _ARCH_PWR4 /* 8-wide x2: G4 only, no 970 data yet (see above) */
+    c->put_pixels_tab[1][1]        = put_pixels8_x2_altivec;
 #endif
     c->put_pixels_tab[1][3]        = put_pixels8_xy2_altivec;
     c->put_pixels_tab[0][3]        = put_pixels16_xy2_altivec;
-- 
2.43.0

