mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
rpng_encode: BGR24 rows swapped to RGB with SSE2/NEON
A BGR24 screenshot - the GPU-readback path - has each row turned into PNG's R,G,B order before filtering. That swap ran a byte at a time, ahead of filters that are all vectorised, and was two thirds of rpng_encode_row: about 12 instructions a pixel, 83 million per 4K capture. SSE2 has no byte shuffle, but this swap needs none. A 16-byte load that starts on a pixel boundary holds every byte's partner two places away in a pattern that repeats every three bytes, so two whole-register byte shifts and three fixed masks build it; each step completes four pixels and steps 12 bytes, its last four bytes rewritten by the next store or the scalar tail. NEON loads the channels as planes (vld3) and stores them back crossed. Other targets keep the scalar loop. PNG output is byte-identical to before: 17 widths from 1 to 1920 x 4 heights x smooth and noisy content x both pitch signs, ASan/UBSan clean. The swap matches the byte loop at every width 0-300 on SSE2, on NEON (aarch64 under qemu) and on the scalar path, and never writes past the row. rpng sample tests pass; builds C89 without warnings and for aarch64 and armv7 NEON. screenshot_io_bench, 3840x2160 BGR24 PNG: 50 million fewer instructions per capture (gradient -4.3%, noise -1.4%); gradient median 69.0 -> 65.9 ms.
This commit is contained in:
1 parent
6d7386a90c
commit
391dbc91b1
1 file changed
+44
-5
@@ -261,14 +261,53 @@ static void copy_rgba_line(uint8_t *dst, const uint8_t *src, unsigned width)
|
||||
memcpy(dst, src, (size_t)width * 4);
|
||||
}
|
||||
|
||||
/* B,G,R -> R,G,B: the outer two bytes of every pixel trade places. This
|
||||
* ran a byte at a time ahead of filters that are all vectorised, and on
|
||||
* a 3840x2160 capture it was two thirds of the row work outside deflate.
|
||||
*
|
||||
* SSE2 has no byte shuffle, but none is needed. A 16-byte load that
|
||||
* starts on a pixel boundary holds each byte's partner two places away,
|
||||
* in a pattern that repeats every three bytes: the red slot takes the
|
||||
* byte two to the right, the blue slot the byte two to the left, green
|
||||
* stays. Two whole-register byte shifts and three fixed masks build the
|
||||
* swap. The first four pixels (12 bytes) are then complete; the last
|
||||
* four bytes are a half-built fifth pixel that the next step's store -
|
||||
* 12 bytes on - overwrites, as does the scalar tail after the last
|
||||
* step. NEON loads the three channels as planes and stores them back
|
||||
* crossed. */
|
||||
static void copy_bgr24_line(uint8_t *dst, const uint8_t *src, unsigned width)
|
||||
{
|
||||
unsigned i;
|
||||
for (i = 0; i < width; i++, dst += 3, src += 3)
|
||||
size_t i = 0;
|
||||
size_t len = (size_t)width * 3;
|
||||
#if defined(RPNG_ENC_SSE2)
|
||||
{
|
||||
dst[2] = src[0];
|
||||
dst[1] = src[1];
|
||||
dst[0] = src[2];
|
||||
const __m128i m_r = _mm_setr_epi8(-1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1);
|
||||
const __m128i m_g = _mm_setr_epi8(0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0);
|
||||
const __m128i m_b = _mm_setr_epi8(0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0);
|
||||
for (; i + 16 <= len; i += 12)
|
||||
{
|
||||
__m128i v = _mm_loadu_si128((const __m128i*)(src + i));
|
||||
_mm_storeu_si128((__m128i*)(dst + i), _mm_or_si128(
|
||||
_mm_and_si128(v, m_g),
|
||||
_mm_or_si128(_mm_and_si128(_mm_srli_si128(v, 2), m_r),
|
||||
_mm_and_si128(_mm_slli_si128(v, 2), m_b))));
|
||||
}
|
||||
}
|
||||
#elif defined(RPNG_ENC_NEON)
|
||||
for (; i + 48 <= len; i += 48)
|
||||
{
|
||||
uint8x16x3_t p = vld3q_u8(src + i);
|
||||
uint8x16_t t = p.val[0];
|
||||
p.val[0] = p.val[2];
|
||||
p.val[2] = t;
|
||||
vst3q_u8(dst + i, p);
|
||||
}
|
||||
#endif
|
||||
for (; i < len; i += 3)
|
||||
{
|
||||
dst[i + 2] = src[i ];
|
||||
dst[i + 1] = src[i + 1];
|
||||
dst[i ] = src[i + 2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user