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
parent
3e892e4a97
commit
d290424f2a
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue