michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "Blur.h" michael@0: michael@0: #include "SSEHelpers.h" michael@0: michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace gfx { michael@0: michael@0: MOZ_ALWAYS_INLINE michael@0: __m128i Divide(__m128i aValues, __m128i aDivisor) michael@0: { michael@0: const __m128i mask = _mm_setr_epi32(0x0, 0xffffffff, 0x0, 0xffffffff); michael@0: static const union { michael@0: int64_t i64[2]; michael@0: __m128i m; michael@0: } roundingAddition = { { int64_t(1) << 31, int64_t(1) << 31 } }; michael@0: michael@0: __m128i multiplied31 = _mm_mul_epu32(aValues, aDivisor); michael@0: __m128i multiplied42 = _mm_mul_epu32(_mm_srli_epi64(aValues, 32), aDivisor); michael@0: michael@0: // Add 1 << 31 before shifting or masking the lower 32 bits away, so that the michael@0: // result is rounded. michael@0: __m128i p_3_1 = _mm_srli_epi64(_mm_add_epi64(multiplied31, roundingAddition.m), 32); michael@0: __m128i p4_2_ = _mm_and_si128(_mm_add_epi64(multiplied42, roundingAddition.m), mask); michael@0: __m128i p4321 = _mm_or_si128(p_3_1, p4_2_); michael@0: return p4321; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE michael@0: __m128i BlurFourPixels(const __m128i& aTopLeft, const __m128i& aTopRight, michael@0: const __m128i& aBottomRight, const __m128i& aBottomLeft, michael@0: const __m128i& aDivisor) michael@0: { michael@0: __m128i values = _mm_add_epi32(_mm_sub_epi32(_mm_sub_epi32(aBottomRight, aTopRight), aBottomLeft), aTopLeft); michael@0: return Divide(values, aDivisor); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE michael@0: void LoadIntegralRowFromRow(uint32_t *aDest, const uint8_t *aSource, michael@0: int32_t aSourceWidth, int32_t aLeftInflation, michael@0: int32_t aRightInflation) michael@0: { michael@0: int32_t currentRowSum = 0; michael@0: michael@0: for (int x = 0; x < aLeftInflation; x++) { michael@0: currentRowSum += aSource[0]; michael@0: aDest[x] = currentRowSum; michael@0: } michael@0: for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { michael@0: currentRowSum += aSource[(x - aLeftInflation)]; michael@0: aDest[x] = currentRowSum; michael@0: } michael@0: for (int x = (aSourceWidth + aLeftInflation); x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { michael@0: currentRowSum += aSource[aSourceWidth - 1]; michael@0: aDest[x] = currentRowSum; michael@0: } michael@0: } michael@0: michael@0: // This function calculates an integral of four pixels stored in the 4 michael@0: // 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns michael@0: // { 30, 80, 160, 260 }. This seems to be the fastest way to do this after michael@0: // much testing. michael@0: MOZ_ALWAYS_INLINE michael@0: __m128i AccumulatePixelSums(__m128i aPixels) michael@0: { michael@0: __m128i sumPixels = aPixels; michael@0: __m128i currentPixels = _mm_slli_si128(aPixels, 4); michael@0: sumPixels = _mm_add_epi32(sumPixels, currentPixels); michael@0: currentPixels = _mm_unpacklo_epi64(_mm_setzero_si128(), sumPixels); michael@0: michael@0: return _mm_add_epi32(sumPixels, currentPixels); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE void michael@0: GenerateIntegralImage_SSE2(int32_t aLeftInflation, int32_t aRightInflation, michael@0: int32_t aTopInflation, int32_t aBottomInflation, michael@0: uint32_t *aIntegralImage, size_t aIntegralImageStride, michael@0: uint8_t *aSource, int32_t aSourceStride, const IntSize &aSize) michael@0: { michael@0: MOZ_ASSERT(!(aLeftInflation & 3)); michael@0: michael@0: uint32_t stride32bit = aIntegralImageStride / 4; michael@0: michael@0: IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, michael@0: aSize.height + aTopInflation + aBottomInflation); michael@0: michael@0: LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, aRightInflation); michael@0: michael@0: for (int y = 1; y < aTopInflation + 1; y++) { michael@0: uint32_t *intRow = aIntegralImage + (y * stride32bit); michael@0: uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; michael@0: uint32_t *intFirstRow = aIntegralImage; michael@0: michael@0: for (int x = 0; x < integralImageSize.width; x += 4) { michael@0: __m128i firstRow = _mm_load_si128((__m128i*)(intFirstRow + x)); michael@0: __m128i previousRow = _mm_load_si128((__m128i*)(intPrevRow + x)); michael@0: _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(firstRow, previousRow)); michael@0: } michael@0: } michael@0: michael@0: for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { michael@0: __m128i currentRowSum = _mm_setzero_si128(); michael@0: uint32_t *intRow = aIntegralImage + (y * stride32bit); michael@0: uint32_t *intPrevRow = aIntegralImage + (y - 1) * stride32bit; michael@0: uint8_t *sourceRow = aSource + aSourceStride * (y - aTopInflation); michael@0: michael@0: uint32_t pixel = sourceRow[0]; michael@0: for (int x = 0; x < aLeftInflation; x += 4) { michael@0: __m128i sumPixels = AccumulatePixelSums(_mm_shuffle_epi32(_mm_set1_epi32(pixel), _MM_SHUFFLE(0, 0, 0, 0))); michael@0: michael@0: sumPixels = _mm_add_epi32(sumPixels, currentRowSum); michael@0: michael@0: currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); michael@0: michael@0: _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); michael@0: } michael@0: for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { michael@0: uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation)); michael@0: michael@0: // It's important to shuffle here. When we exit this loop currentRowSum michael@0: // has to be set to sumPixels, so that the following loop can get the michael@0: // correct pixel for the currentRowSum. The highest order pixel in michael@0: // currentRowSum could've originated from accumulation in the stride. michael@0: currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); michael@0: michael@0: __m128i sumPixels = AccumulatePixelSums(_mm_unpacklo_epi16(_mm_unpacklo_epi8( _mm_set1_epi32(pixels), _mm_setzero_si128()), _mm_setzero_si128())); michael@0: sumPixels = _mm_add_epi32(sumPixels, currentRowSum); michael@0: michael@0: currentRowSum = sumPixels; michael@0: michael@0: _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); michael@0: } michael@0: michael@0: pixel = sourceRow[aSize.width - 1]; michael@0: int x = (aSize.width + aLeftInflation); michael@0: if ((aSize.width & 3)) { michael@0: // Deal with unaligned portion. Get the correct pixel from currentRowSum, michael@0: // see explanation above. michael@0: uint32_t intCurrentRowSum = ((uint32_t*)¤tRowSum)[(aSize.width % 4) - 1]; michael@0: for (; x < integralImageSize.width; x++) { michael@0: // We could be unaligned here! michael@0: if (!(x & 3)) { michael@0: // aligned! michael@0: currentRowSum = _mm_set1_epi32(intCurrentRowSum); michael@0: break; michael@0: } michael@0: intCurrentRowSum += pixel; michael@0: intRow[x] = intPrevRow[x] + intCurrentRowSum; michael@0: } michael@0: } else { michael@0: currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); michael@0: } michael@0: for (; x < integralImageSize.width; x += 4) { michael@0: __m128i sumPixels = AccumulatePixelSums(_mm_set1_epi32(pixel)); michael@0: michael@0: sumPixels = _mm_add_epi32(sumPixels, currentRowSum); michael@0: michael@0: currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); michael@0: michael@0: _mm_store_si128((__m128i*)(intRow + x), _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); michael@0: } michael@0: } michael@0: michael@0: if (aBottomInflation) { michael@0: // Store the last valid row of our source image in the last row of michael@0: // our integral image. This will be overwritten with the correct values michael@0: // in the upcoming loop. michael@0: LoadIntegralRowFromRow(aIntegralImage + (integralImageSize.height - 1) * stride32bit, michael@0: aSource + (aSize.height - 1) * aSourceStride, aSize.width, aLeftInflation, aRightInflation); michael@0: michael@0: michael@0: for (int y = aSize.height + aTopInflation; y < integralImageSize.height; y++) { michael@0: __m128i *intRow = (__m128i*)(aIntegralImage + (y * stride32bit)); michael@0: __m128i *intPrevRow = (__m128i*)(aIntegralImage + (y - 1) * stride32bit); michael@0: __m128i *intLastRow = (__m128i*)(aIntegralImage + (integralImageSize.height - 1) * stride32bit); michael@0: michael@0: for (int x = 0; x < integralImageSize.width; x += 4) { michael@0: _mm_store_si128(intRow + (x / 4), michael@0: _mm_add_epi32(_mm_load_si128(intLastRow + (x / 4)), michael@0: _mm_load_si128(intPrevRow + (x / 4)))); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Attempt to do an in-place box blur using an integral image. michael@0: */ michael@0: void michael@0: AlphaBoxBlur::BoxBlur_SSE2(uint8_t* aData, michael@0: int32_t aLeftLobe, michael@0: int32_t aRightLobe, michael@0: int32_t aTopLobe, michael@0: int32_t aBottomLobe, michael@0: uint32_t *aIntegralImage, michael@0: size_t aIntegralImageStride) michael@0: { michael@0: IntSize size = GetSize(); michael@0: michael@0: MOZ_ASSERT(size.height > 0); michael@0: michael@0: // Our 'left' or 'top' lobe will include the current pixel. i.e. when michael@0: // looking at an integral image the value of a pixel at 'x,y' is calculated michael@0: // using the value of the integral image values above/below that. michael@0: aLeftLobe++; michael@0: aTopLobe++; michael@0: int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); michael@0: michael@0: MOZ_ASSERT(boxSize > 0); michael@0: michael@0: if (boxSize == 1) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); michael@0: michael@0: uint32_t stride32bit = aIntegralImageStride / 4; michael@0: int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); michael@0: michael@0: GenerateIntegralImage_SSE2(leftInflation, aRightLobe, aTopLobe, aBottomLobe, michael@0: aIntegralImage, aIntegralImageStride, aData, michael@0: mStride, size); michael@0: michael@0: __m128i divisor = _mm_set1_epi32(reciprocal); michael@0: michael@0: // This points to the start of the rectangle within the IntegralImage that overlaps michael@0: // the surface being blurred. michael@0: uint32_t *innerIntegral = aIntegralImage + (aTopLobe * stride32bit) + leftInflation; michael@0: michael@0: IntRect skipRect = mSkipRect; michael@0: int32_t stride = mStride; michael@0: uint8_t *data = aData; michael@0: for (int32_t y = 0; y < size.height; y++) { michael@0: bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); michael@0: michael@0: uint32_t *topLeftBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); michael@0: uint32_t *topRightBase = innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); michael@0: uint32_t *bottomRightBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); michael@0: uint32_t *bottomLeftBase = innerIntegral + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); michael@0: michael@0: int32_t x = 0; michael@0: // Process 16 pixels at a time for as long as possible. michael@0: for (; x <= size.width - 16; x += 16) { michael@0: if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { michael@0: x = skipRect.XMost() - 16; michael@0: // Trigger early jump on coming loop iterations, this will be reset michael@0: // next line anyway. michael@0: inSkipRectY = false; michael@0: continue; michael@0: } michael@0: michael@0: __m128i topLeft; michael@0: __m128i topRight; michael@0: __m128i bottomRight; michael@0: __m128i bottomLeft; michael@0: michael@0: topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); michael@0: topRight = loadUnaligned128((__m128i*)(topRightBase + x)); michael@0: bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); michael@0: bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); michael@0: __m128i result1 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); michael@0: michael@0: topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 4)); michael@0: topRight = loadUnaligned128((__m128i*)(topRightBase + x + 4)); michael@0: bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 4)); michael@0: bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 4)); michael@0: __m128i result2 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); michael@0: michael@0: topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 8)); michael@0: topRight = loadUnaligned128((__m128i*)(topRightBase + x + 8)); michael@0: bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 8)); michael@0: bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 8)); michael@0: __m128i result3 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); michael@0: michael@0: topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 12)); michael@0: topRight = loadUnaligned128((__m128i*)(topRightBase + x + 12)); michael@0: bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 12)); michael@0: bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 12)); michael@0: __m128i result4 = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); michael@0: michael@0: __m128i final = _mm_packus_epi16(_mm_packs_epi32(result1, result2), _mm_packs_epi32(result3, result4)); michael@0: michael@0: _mm_storeu_si128((__m128i*)(data + stride * y + x), final); michael@0: } michael@0: michael@0: // Process the remaining pixels 4 bytes at a time. michael@0: for (; x < size.width; x += 4) { michael@0: if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { michael@0: x = skipRect.XMost() - 4; michael@0: // Trigger early jump on coming loop iterations, this will be reset michael@0: // next line anyway. michael@0: inSkipRectY = false; michael@0: continue; michael@0: } michael@0: __m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); michael@0: __m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x)); michael@0: __m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); michael@0: __m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); michael@0: michael@0: __m128i result = BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); michael@0: __m128i final = _mm_packus_epi16(_mm_packs_epi32(result, _mm_setzero_si128()), _mm_setzero_si128()); michael@0: michael@0: *(uint32_t*)(data + stride * y + x) = _mm_cvtsi128_si32(final); michael@0: } michael@0: } michael@0: michael@0: } michael@0: michael@0: } michael@0: }