Re: Concerns about using filters for downscaling
Owen Taylor <[email protected]>
| Newsgroups | gmane.comp.lib.cairo |
|---|---|
| Message-ID | <[email protected]> |
On Sun, 2014-03-23 at 02:30 +0100, Søren Sandmann wrote: > Owen Taylor <[email protected]> writes: > > > (Søren pointed out on IRC that the pixman implemention of convolution > > is not SIMD optimized, so their would be future opportunity to improve > > what we can do within the constraints of 5-10x slower.) I took a stab at implementing a pretty straightforward port of the pixman-fast-path.c code to SSE2 - attached. I was able to get roughly a 2x speedup: > scale NEAREST BILINEAR GOOD BEST > ---- ------ -------- ----- ------ > 1.1 656 2860 15163 149147 > 1.5 350 1574 7918 108820 > 1.9 220 965 7176 89324 > 2.0 51 873 6397 95665 > 2.5 34 114 4331 71975 > 3.0 25 392 4074 67211 > 3.5 66 290 3101 60168 > 4.0 15 223 3135 59207 > 4.5 12 37 2524 58586 > 5.0 34 143 2643 56798 scale NEAREST BILINEAR GOOD BEST MIPMAP ----- ------- -------- ---- ---- ------ 1.1 478 3073 10267 74341 3022 1.5 264 1662 5549 53639 1680 1.9 171 1053 3467 50495 1058 2.0 60 943 3080 46139 151 2.5 40 115 2709 40281 729 3.0 29 431 1907 37104 556 3.5 55 324 1878 35542 439 4.0 19 244 1515 34569 173 4.5 16 39 1451 33327 364 5.0 30 157 1219 33239 330 I've added another column which is an implementation (in my test program) of the technique of creating a temporary power-of-two scaled down image and bilinear-filtering from that. As you can see, even tossing away the mipmaps, the performance is much better than with convolution - even though either way we have to touch all the pixels in the source image, it's much easier to write fast, cache friendly average-of-four-pixels code, then convolution code. > Also, the pixman convolution filter is designed to be implemented in a > separable way, but the current code doesn't do that. This optimization > should be another major speedup for filters with wide support such as as > Lanczos. What's the idea? Just an optimization of the per-pixel convolution? creating an intermediate image that is a 1D convolution of the source image and then convolving that the other way? (That seems only be possible for strict scales.) Mostly, the timings above don't change my opinion - even if we're only slowing down GOOD by 3-5x rather than 5-10x, that's still a huge performance regression for image-based applications and platforms. And even if only slowing down BEST by 30-50x rather than 50-100x, that's still creating a trap for anybody who happened to specify it in their application. - Owen -- cairo mailing list [email protected] http://lists.cairographics.org/mailman/listinfo/cairo
0001-Add-a-SSE2-fast-path-for-separable-convolutions.patch
(text/x-patch, 13.1 KB)
From 8b8fd5ebc9899ff4985ff50c48ee2bf1ab75feea Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" <[email protected]> Date: Mon, 24 Mar 2014 16:02:28 -0400 Subject: [PATCH] Add a SSE2 fast path for separable convolutions Take the fast-path code for separable convolutions, and port it to use SSE2 floating point math. A special case is added for when the convolution is reading entirely within the un-repeated bounds of the source image, and the per-source-pixel checks for 0 filter values are removed because they slowed down filters with mostly non-zero values significantly. The SSE2 conversion by itself only gave minimal speedups, because the arithmetic was hidden by the rest of the complexity, but with the tight inner loop, the overall speedup is a doubling. --- pixman/pixman-sse2.c | 308 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 306 insertions(+), 2 deletions(-) diff --git a/pixman/pixman-sse2.c b/pixman/pixman-sse2.c index a6e7808..cbc16f5 100644 --- a/pixman/pixman-sse2.c +++ b/pixman/pixman-sse2.c @@ -6460,12 +6460,282 @@ sse2_fetch_a8 (pixman_iter_t *iter, const uint32_t *mask) return iter->buffer; } +typedef uint32_t (* convert_pixel_t) (const uint8_t *row, int x); + +static void +sse2_iter_fini_separable_convolution (pixman_iter_t *iter) +{ + free (iter->data); +} + +static void +sse2_iter_init_separable_convolution (pixman_iter_t *iter, + const pixman_iter_info_t *info) +{ + pixman_fixed_t *params = iter->image->common.filter_params; + int n_params = iter->image->common.n_filter_params; + float *filter; + int i; + + iter->data = malloc (sizeof (float) * (n_params - 4)); + if (iter->data == NULL) + goto fail; + + filter = (float *)iter->data; + + for (i = 0; i < (n_params - 4); i++) + filter[i] = (float)params[i + 4] / 65536.; + + iter->fini = sse2_iter_fini_separable_convolution; + + return; + +fail: + /* In case of OOM, we don't guarantee any particular rendering. + */ + _pixman_log_error ( + FUNC, "Allocation failure, skipping rendering\n"); + + iter->get_scanline = _pixman_iter_get_scanline_noop; + iter->fini = NULL; +} + +/* Expands a pixel packed into a 32-bit integer into 4 32-bit floats */ +static force_inline __m128 +expand_pixel_ps (uint32_t pixel) +{ + __m128i pixel_wide = _mm_cvtsi32_si128 (pixel); + __m128i pixel_unpacked = _mm_unpacklo_epi16 (_mm_unpacklo_epi8 (pixel_wide, _mm_setzero_si128 ()), + _mm_setzero_si128 ()); + return _mm_cvtepi32_ps (pixel_unpacked); +} + +static force_inline void +sse2_fetch_separable_convolution_affine (pixman_image_t * image, + float *filter, + int offset, + int line, + int width, + uint32_t * buffer, + const uint32_t * mask, + + convert_pixel_t convert_pixel, + pixman_format_code_t format, + pixman_repeat_t repeat_mode) +{ + __m128 one_half = _mm_set1_ps (0.5f); + bits_image_t *bits = &image->bits; + pixman_fixed_t *params = image->common.filter_params; + int cwidth = pixman_fixed_to_int (params[0]); + int cheight = pixman_fixed_to_int (params[1]); + int x_off = ((cwidth << 16) - pixman_fixed_1) >> 1; + int y_off = ((cheight << 16) - pixman_fixed_1) >> 1; + int x_phase_bits = pixman_fixed_to_int (params[2]); + int y_phase_bits = pixman_fixed_to_int (params[3]); + int x_phase_shift = 16 - x_phase_bits; + int y_phase_shift = 16 - y_phase_bits; + pixman_fixed_t vx, vy; + pixman_fixed_t ux, uy; + pixman_vector_t v; + int k; + + /* reference point is the center of the pixel */ + v.vector[0] = pixman_int_to_fixed (offset) + pixman_fixed_1 / 2; + v.vector[1] = pixman_int_to_fixed (line) + pixman_fixed_1 / 2; + v.vector[2] = pixman_fixed_1; + + if (!pixman_transform_point_3d (image->common.transform, &v)) + return; + + ux = image->common.transform->matrix[0][0]; + uy = image->common.transform->matrix[1][0]; + + vx = v.vector[0]; + vy = v.vector[1]; + + for (k = 0; k < width; ++k) + { + float *y_filter; + __m128 stot; + __m64 tot_integer, tot_packed; + pixman_fixed_t x, y; + int32_t x1, x2, y1, y2; + int32_t px, py; + int i, j; + + if (mask && !mask[k]) + goto next; + + /* Round x and y to the middle of the closest phase before continuing. This + * ensures that the convolution matrix is aligned right, since it was + * positioned relative to a particular phase (and not relative to whatever + * exact fraction we happen to get here). + */ + x = ((vx >> x_phase_shift) << x_phase_shift) + ((1 << x_phase_shift) >> 1); + y = ((vy >> y_phase_shift) << y_phase_shift) + ((1 << y_phase_shift) >> 1); + + px = (x & 0xffff) >> x_phase_shift; + py = (y & 0xffff) >> y_phase_shift; + + x1 = pixman_fixed_to_int (x - pixman_fixed_e - x_off); + y1 = pixman_fixed_to_int (y - pixman_fixed_e - y_off); + x2 = x1 + cwidth; + y2 = y1 + cheight; + + stot = _mm_setzero_ps(); + + y_filter = filter + (1 << x_phase_bits) * cwidth + py * cheight; + + if (x1 >= 0 && y1 >= 0 && x2 <= bits->width && y2 <= bits->height) + { + for (i = y1; i < y2; ++i) + { + float fy = *y_filter++; + float *x_filter = filter + px * cwidth; + + for (j = x1; j < x2; ++j) + { + float fx = *x_filter++; + int rx = j; + int ry = i; + + __m128 f, pixel_ps; + uint32_t pixel, mask; + uint8_t *row; + + mask = PIXMAN_FORMAT_A (format)? 0 : 0xff000000; + + row = (uint8_t *)bits->bits + bits->rowstride * 4 * ry; + pixel = convert_pixel (row, rx) | mask; + + f = _mm_set1_ps (fx * fy); + pixel_ps = expand_pixel_ps (pixel); + stot = _mm_add_ps(stot, _mm_mul_ps (pixel_ps, f)); + } + } + } + else + { + for (i = y1; i < y2; ++i) + { + float fy = *y_filter++; + float *x_filter = filter + px * cwidth; + + for (j = x1; j < x2; ++j) + { + float fx = *x_filter++; + int rx = j; + int ry = i; + + __m128 f, pixel_ps; + uint32_t pixel, mask; + uint8_t *row; + + mask = PIXMAN_FORMAT_A (format)? 0 : 0xff000000; + + if (repeat_mode != PIXMAN_REPEAT_NONE) + { + repeat (repeat_mode, &rx, bits->width); + repeat (repeat_mode, &ry, bits->height); + + row = (uint8_t *)bits->bits + bits->rowstride * 4 * ry; + pixel = convert_pixel (row, rx) | mask; + } + else + { + if (rx < 0 || ry < 0 || rx >= bits->width || ry >= bits->height) + { + pixel = 0; + } + else + { + row = (uint8_t *)bits->bits + bits->rowstride * 4 * ry; + pixel = convert_pixel (row, rx) | mask; + } + } + + f = _mm_set1_ps (fx * fy); + pixel_ps = expand_pixel_ps (pixel); + stot = _mm_add_ps(stot, _mm_mul_ps (pixel_ps, f)); + } + } + } + + tot_integer = _mm_cvtps_pi16 (_mm_add_ps (stot, one_half)); + tot_packed = _mm_packs_pu16 (tot_integer, tot_integer); + buffer[k] = _mm_cvtsi64_si32 (tot_packed); + + next: + vx += ux; + vy += uy; + } +} + +static force_inline uint32_t +convert_a8r8g8b8 (const uint8_t *row, int x) +{ + return *(((uint32_t *)row) + x); +} + +static force_inline uint32_t +convert_x8r8g8b8 (const uint8_t *row, int x) +{ + return *(((uint32_t *)row) + x); +} + +static force_inline uint32_t +convert_a8 (const uint8_t *row, int x) +{ + return *(row + x) << 24; +} + +static force_inline uint32_t +convert_r5g6b5 (const uint8_t *row, int x) +{ + return convert_0565_to_0888 (*((uint16_t *)row + x)); +} + +#define MAKE_SEPARABLE_CONVOLUTION_FETCHER(name, format, repeat_mode) \ + static uint32_t * \ + sse2_fetch_separable_convolution_affine_ ## name (pixman_iter_t *iter, \ + const uint32_t * mask) \ + { \ + sse2_fetch_separable_convolution_affine ( \ + iter->image, \ + (float *)iter->data, \ + iter->x, iter->y++, \ + iter->width, \ + iter->buffer, mask, \ + convert_ ## format, \ + PIXMAN_ ## format, \ + repeat_mode); \ + \ + return iter->buffer; \ + } + +MAKE_SEPARABLE_CONVOLUTION_FETCHER (pad_a8r8g8b8, a8r8g8b8, PIXMAN_REPEAT_PAD) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (none_a8r8g8b8, a8r8g8b8, PIXMAN_REPEAT_NONE) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (reflect_a8r8g8b8, a8r8g8b8, PIXMAN_REPEAT_REFLECT) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (normal_a8r8g8b8, a8r8g8b8, PIXMAN_REPEAT_NORMAL) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (pad_x8r8g8b8, x8r8g8b8, PIXMAN_REPEAT_PAD) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (none_x8r8g8b8, x8r8g8b8, PIXMAN_REPEAT_NONE) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (reflect_x8r8g8b8, x8r8g8b8, PIXMAN_REPEAT_REFLECT) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (normal_x8r8g8b8, x8r8g8b8, PIXMAN_REPEAT_NORMAL) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (pad_a8, a8, PIXMAN_REPEAT_PAD) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (none_a8, a8, PIXMAN_REPEAT_NONE) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (reflect_a8, a8, PIXMAN_REPEAT_REFLECT) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (normal_a8, a8, PIXMAN_REPEAT_NORMAL) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (pad_r5g6b5, r5g6b5, PIXMAN_REPEAT_PAD) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (none_r5g6b5, r5g6b5, PIXMAN_REPEAT_NONE) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (reflect_r5g6b5, r5g6b5, PIXMAN_REPEAT_REFLECT) +MAKE_SEPARABLE_CONVOLUTION_FETCHER (normal_r5g6b5, r5g6b5, PIXMAN_REPEAT_NORMAL) + +static const pixman_iter_info_t sse2_iters[] = +{ #define IMAGE_FLAGS \ (FAST_PATH_STANDARD_FLAGS | FAST_PATH_ID_TRANSFORM | \ FAST_PATH_BITS_IMAGE | FAST_PATH_SAMPLES_COVER_CLIP_NEAREST) -static const pixman_iter_info_t sse2_iters[] = -{ { PIXMAN_x8r8g8b8, IMAGE_FLAGS, ITER_NARROW, _pixman_iter_init_bits_stride, sse2_fetch_x8r8g8b8, NULL }, @@ -6475,6 +6745,40 @@ static const pixman_iter_info_t sse2_iters[] = { PIXMAN_a8, IMAGE_FLAGS, ITER_NARROW, _pixman_iter_init_bits_stride, sse2_fetch_a8, NULL }, + +#define GENERAL_SEPARABLE_CONVOLUTION_FLAGS \ + (FAST_PATH_NO_ALPHA_MAP | \ + FAST_PATH_NO_ACCESSORS | \ + FAST_PATH_HAS_TRANSFORM | \ + FAST_PATH_AFFINE_TRANSFORM | \ + FAST_PATH_SEPARABLE_CONVOLUTION_FILTER) + +#define SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH(name, format, repeat) \ + { PIXMAN_ ## format, \ + GENERAL_SEPARABLE_CONVOLUTION_FLAGS | FAST_PATH_ ## repeat ## _REPEAT, \ + ITER_NARROW | ITER_SRC, \ + sse2_iter_init_separable_convolution, \ + sse2_fetch_separable_convolution_affine_ ## name, \ + NULL, \ + }, + + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (pad_a8r8g8b8, a8r8g8b8, PAD) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (none_a8r8g8b8, a8r8g8b8, NONE) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (reflect_a8r8g8b8, a8r8g8b8, REFLECT) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (normal_a8r8g8b8, a8r8g8b8, NORMAL) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (pad_x8r8g8b8, x8r8g8b8, PAD) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (none_x8r8g8b8, x8r8g8b8, NONE) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (reflect_x8r8g8b8, x8r8g8b8, REFLECT) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (normal_x8r8g8b8, x8r8g8b8, NORMAL) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (pad_a8, a8, PAD) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (none_a8, a8, NONE) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (reflect_a8, a8, REFLECT) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (normal_a8, a8, NORMAL) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (pad_r5g6b5, r5g6b5, PAD) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (none_r5g6b5, r5g6b5, NONE) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (reflect_r5g6b5, r5g6b5, REFLECT) + SEPARABLE_CONVOLUTION_AFFINE_FAST_PATH (normal_r5g6b5, r5g6b5, NORMAL) + { PIXMAN_null }, }; -- 1.8.5.3