From 077a3eb2bf74db10282d427175282bcc1462f8d6 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov 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 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