NEON optimized bilinear sampling

Adds NEON version of interpolate_4_pixels used by smooth upscaling, and
bilinear sampling.

The SSE2 version is reordered to match the NEON version so they have
the same order of operations and a faster version that loads directly
into vector registers.

Testing is extended so we have a test of smoothness that can catch more
possible mistakes.

Change-Id: I0de4aecf5cb79468e7c8f19f421aa24b2955547c
Reviewed-by: Erik Verbruggen <erik.verbruggen@theqtcompany.com>
bb10
Allan Sandfeld Jensen 2015-12-14 12:49:36 +01:00 committed by Tor Arne Vestbø
parent 3e892e4a97
commit d290424f2a
4 changed files with 122 additions and 42 deletions

View File

@ -2342,8 +2342,8 @@ static const uint * QT_FASTCALL fetchTransformedBilinearARGB32PM(uint *buffer, c
uint bl = s2[x1];
uint br = s2[x2];
#if defined(__SSE2__)
// The SSE2 optimized interpolate_4_pixels is faster than interpolate_4_pixels_16.
#if defined(__SSE2__) || defined(__ARM_NEON__)
// The optimized interpolate_4_pixels are faster than interpolate_4_pixels_16.
int distx = (fx & 0x0000ffff) >> 8;
int disty = (fy & 0x0000ffff) >> 8;
*b = interpolate_4_pixels(tl, tr, bl, br, distx, disty);
@ -2572,12 +2572,8 @@ static const uint *QT_FASTCALL fetchTransformedBilinear(uint *buffer, const Oper
if ((fdx < 0 && fdx > -(fixed_scale / 8)) || std::abs(data->m22) < (1./8.)) { // scale up more than 8x
int disty = (fy & 0x0000ffff) >> 8;
for (int i = 0; i < len; ++i) {
uint tl = buf1[i * 2 + 0];
uint tr = buf1[i * 2 + 1];
uint bl = buf2[i * 2 + 0];
uint br = buf2[i * 2 + 1];
int distx = (fracX & 0x0000ffff) >> 8;
b[i] = interpolate_4_pixels(tl, tr, bl, br, distx, disty);
b[i] = interpolate_4_pixels(buf1 + i * 2, buf2 + i * 2, distx, disty);
fracX += fdx;
}
} else { //scale down
@ -2638,15 +2634,10 @@ static const uint *QT_FASTCALL fetchTransformedBilinear(uint *buffer, const Oper
if (std::abs(data->m11) > 8 || std::abs(data->m22) > 8) {
//if we are zooming more than 8 times, we use 8bit precision for the position.
for (int i = 0; i < len; ++i) {
uint tl = buf1[i * 2 + 0];
uint tr = buf1[i * 2 + 1];
uint bl = buf2[i * 2 + 0];
uint br = buf2[i * 2 + 1];
int distx = (fracX & 0x0000ffff) >> 8;
int disty = (fracY & 0x0000ffff) >> 8;
b[i] = interpolate_4_pixels(tl, tr, bl, br, distx, disty);
b[i] = interpolate_4_pixels(buf1 + i * 2, buf2 + i * 2, distx, disty);
fracX += fdx;
fracY += fdy;
}
@ -2736,12 +2727,7 @@ static const uint *QT_FASTCALL fetchTransformedBilinear(uint *buffer, const Oper
int distx = distxs[i];
int disty = distys[i];
uint tl = buf1[i * 2 + 0];
uint tr = buf1[i * 2 + 1];
uint bl = buf2[i * 2 + 0];
uint br = buf2[i * 2 + 1];
b[i] = interpolate_4_pixels(tl, tr, bl, br, distx, disty);
b[i] = interpolate_4_pixels(buf1 + i * 2, buf2 + i * 2, distx, disty);
}
length -= len;
b += len;

View File

@ -629,31 +629,75 @@ static Q_ALWAYS_INLINE uint BYTE_MUL(uint x, uint a) {
}
#endif
#ifdef __SSE2__
#if defined(__SSE2__)
static Q_ALWAYS_INLINE uint interpolate_4_pixels_sse2(__m128i vt, __m128i vb, uint distx, uint disty)
{
// First interpolate top and bottom pixels in parallel.
vt = _mm_unpacklo_epi8(vt, _mm_setzero_si128());
vb = _mm_unpacklo_epi8(vb, _mm_setzero_si128());
vt = _mm_mullo_epi16(vt, _mm_set1_epi16(256 - disty));
vb = _mm_mullo_epi16(vb, _mm_set1_epi16(disty));
__m128i vlr = _mm_add_epi16(vt, vb);
vlr = _mm_srli_epi16(vlr, 8);
// vlr now contains the result of the first two interpolate calls vlr = unpacked((xright << 64) | xleft)
// Now the last interpolate between left and right..
const __m128i vidistx = _mm_shufflelo_epi16(_mm_cvtsi32_si128(256 - distx), _MM_SHUFFLE(0, 0, 0, 0));
const __m128i vdistx = _mm_shufflelo_epi16(_mm_cvtsi32_si128(distx), _MM_SHUFFLE(0, 0, 0, 0));
const __m128i vmulx = _mm_unpacklo_epi16(vidistx, vdistx);
vlr = _mm_unpacklo_epi16(vlr, _mm_srli_si128(vlr, 8));
// vlr now contains the colors of left and right interleaved { la, ra, lr, rr, lg, rg, lb, rb }
vlr = _mm_madd_epi16(vlr, vmulx); // Multiply and horizontal add.
vlr = _mm_srli_epi32(vlr, 8);
vlr = _mm_packs_epi32(vlr, vlr);
vlr = _mm_packus_epi16(vlr, vlr);
return _mm_cvtsi128_si32(vlr);
}
static inline uint interpolate_4_pixels(uint tl, uint tr, uint bl, uint br, uint distx, uint disty)
{
// First interpolate right and left pixels in parallel.
__m128i vl = _mm_unpacklo_epi32(_mm_cvtsi32_si128(tl), _mm_cvtsi32_si128(bl));
__m128i vr = _mm_unpacklo_epi32(_mm_cvtsi32_si128(tr), _mm_cvtsi32_si128(br));
vl = _mm_unpacklo_epi8(vl, _mm_setzero_si128());
vr = _mm_unpacklo_epi8(vr, _mm_setzero_si128());
vl = _mm_mullo_epi16(vl, _mm_set1_epi16(256 - distx));
vr = _mm_mullo_epi16(vr, _mm_set1_epi16(distx));
__m128i vtb = _mm_add_epi16(vl, vr);
vtb = _mm_srli_epi16(vtb, 8);
// vtb now contains the result of the first two interpolate calls vtb = unpacked((xbot << 64) | xtop)
__m128i vt = _mm_unpacklo_epi32(_mm_cvtsi32_si128(tl), _mm_cvtsi32_si128(tr));
__m128i vb = _mm_unpacklo_epi32(_mm_cvtsi32_si128(bl), _mm_cvtsi32_si128(br));
return interpolate_4_pixels_sse2(vt, vb, distx, disty);
}
// Now the last interpolate between top and bottom interpolations.
const __m128i vidisty = _mm_shufflelo_epi16(_mm_cvtsi32_si128(256 - disty), _MM_SHUFFLE(0, 0, 0, 0));
const __m128i vdisty = _mm_shufflelo_epi16(_mm_cvtsi32_si128(disty), _MM_SHUFFLE(0, 0, 0, 0));
const __m128i vmuly = _mm_unpacklo_epi16(vidisty, vdisty);
vtb = _mm_unpacklo_epi16(vtb, _mm_srli_si128(vtb, 8));
// vtb now contains the colors of top and bottom interleaved { ta, ba, tr, br, tg, bg, tb, bb }
vtb = _mm_madd_epi16(vtb, vmuly); // Multiply and horizontal add.
vtb = _mm_srli_epi32(vtb, 8);
vtb = _mm_packs_epi32(vtb, _mm_setzero_si128());
vtb = _mm_packus_epi16(vtb, _mm_setzero_si128());
return _mm_cvtsi128_si32(vtb);
static inline uint interpolate_4_pixels(const uint t[], const uint b[], uint distx, uint disty)
{
__m128i vt = _mm_loadl_epi64((const __m128i*)t);
__m128i vb = _mm_loadl_epi64((const __m128i*)b);
return interpolate_4_pixels_sse2(vt, vb, distx, disty);
}
#elif defined(__ARM_NEON__)
static Q_ALWAYS_INLINE uint interpolate_4_pixels_neon(uint32x2_t vt32, uint32x2_t vb32, uint distx, uint disty)
{
uint16x8_t vt16 = vmovl_u8(vreinterpret_u8_u32(vt32));
uint16x8_t vb16 = vmovl_u8(vreinterpret_u8_u32(vb32));
vt16 = vmulq_n_u16(vt16, 256 - disty);
vt16 = vmlaq_n_u16(vt16, vb16, disty);
vt16 = vshrq_n_u16(vt16, 8);
uint16x4_t vl16 = vget_low_u16(vt16);
uint16x4_t vr16 = vget_high_u16(vt16);
vl16 = vmul_n_u16(vl16, 256 - distx);
vl16 = vmla_n_u16(vl16, vr16, distx);
vl16 = vshr_n_u16(vl16, 8);
uint8x8_t vr = vmovn_u16(vcombine_u16(vl16, vl16));
return vget_lane_u32(vreinterpret_u32_u8(vr), 0);
}
static inline uint interpolate_4_pixels(uint tl, uint tr, uint bl, uint br, uint distx, uint disty)
{
uint32x2_t vt32 = vmov_n_u32(tl);
uint32x2_t vb32 = vmov_n_u32(bl);
vt32 = vset_lane_u32(tr, vt32, 1);
vb32 = vset_lane_u32(br, vb32, 1);
return interpolate_4_pixels_neon(vt32, vb32, distx, disty);
}
static inline uint interpolate_4_pixels(const uint t[], const uint b[], uint distx, uint disty)
{
uint32x2_t vt32 = vld1_u32(t);
uint32x2_t vb32 = vld1_u32(b);
return interpolate_4_pixels_neon(vt32, vb32, distx, disty);
}
#else
static inline uint interpolate_4_pixels(uint tl, uint tr, uint bl, uint br, uint distx, uint disty)
@ -664,6 +708,11 @@ static inline uint interpolate_4_pixels(uint tl, uint tr, uint bl, uint br, uint
uint xbot = INTERPOLATE_PIXEL_256(bl, idistx, br, distx);
return INTERPOLATE_PIXEL_256(xtop, idisty, xbot, disty);
}
static inline uint interpolate_4_pixels(const uint t[], const uint b[], uint distx, uint disty)
{
return interpolate_4_pixels(t[0], t[1], b[0], b[1], distx, disty);
}
#endif
#if Q_BYTE_ORDER == Q_BIG_ENDIAN

View File

@ -308,7 +308,7 @@ static void qt_qimageScaleAARGBA_up_xy(QImageScaleInfo *isi, unsigned int *dest,
const unsigned int *pix = sptr + xpoints[x];
const int xap = xapoints[x];
if (xap > 0)
*dptr = interpolate_4_pixels(pix[0], pix[1], pix[sow], pix[sow + 1], xap, yap);
*dptr = interpolate_4_pixels(pix, pix + sow, xap, yap);
else
*dptr = INTERPOLATE_PIXEL_256(pix[0], 256 - yap, pix[sow], yap);
dptr++;

View File

@ -112,6 +112,7 @@ private slots:
void smoothScale2_data();
void smoothScale2();
void smoothScale3();
void smoothScale4();
void smoothScaleBig();
void smoothScaleAlpha();
@ -1677,6 +1678,30 @@ void tst_QImage::smoothScale2()
QCOMPARE(qBlue(pixel), qBlue(expected));
}
// scale x up
scaled = img.scaled(QSize(size, size * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
for (int y = 0; y < scaled.height(); ++y) {
for (int x = 0; x < scaled.width(); ++x) {
pixel = scaled.pixel(x, y);
QCOMPARE(qAlpha(pixel), qAlpha(expected));
QCOMPARE(qRed(pixel), qRed(expected));
QCOMPARE(qGreen(pixel), qGreen(expected));
QCOMPARE(qBlue(pixel), qBlue(expected));
}
}
// scale y up
scaled = img.scaled(QSize(size * 2, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
for (int y = 0; y < scaled.height(); ++y) {
for (int x = 0; x < scaled.width(); ++x) {
pixel = scaled.pixel(x, y);
QCOMPARE(qAlpha(pixel), qAlpha(expected));
QCOMPARE(qRed(pixel), qRed(expected));
QCOMPARE(qGreen(pixel), qGreen(expected));
QCOMPARE(qBlue(pixel), qBlue(expected));
}
}
// scale x up, y up
scaled = img.scaled(QSize(size * 2, size * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
for (int y = 0; y < scaled.height(); ++y) {
@ -1742,6 +1767,26 @@ void tst_QImage::smoothScale3()
}
}
// Tests smooth upscale is smooth
void tst_QImage::smoothScale4()
{
QImage img(4, 4, QImage::Format_RGB32);
for (int y = 0; y < 4; ++y) {
for (int x = 0; x < 4; ++x) {
img.setPixel(x, y, qRgb(x * 255 / 3, y * 255 / 3, 0));
}
}
QImage scaled = img.scaled(37, 23, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
for (int y = 0; y < scaled.height(); ++y) {
for (int x = 0; x < scaled.width(); ++x) {
if (x > 0)
QVERIFY(qRed(scaled.pixel(x, y)) >= qRed(scaled.pixel(x - 1, y)));
if (y > 0)
QVERIFY(qGreen(scaled.pixel(x, y)) >= qGreen(scaled.pixel(x, y - 1)));
}
}
}
void tst_QImage::smoothScaleBig()
{
#if defined(Q_OS_WINCE)