/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MOZILLA_GFX_SWIZZLE_NEON_H_ #define MOZILLA_GFX_SWIZZLE_NEON_H_ #ifdef MOZILLA_GFX_SWIZZLE_GENERIC_H_ # error "SwizzleNEON.h must be included before SwizzleGeneric.h" #endif #include #include "SwizzleGenericDecls.h" namespace mozilla::gfx { #ifdef __aarch64__ using mozneon = xsimd::neon64; #else using mozneon = xsimd::neon; #endif extern const uint32_t sUnpremultiplyTable_NEON[256]; template requires std::same_as || std::same_as static MOZ_ALWAYS_INLINE xsimd::batch LoadRemainder_SIMD( const uint8_t* aSrc, size_t aLength) { const uint32_t* src32 = reinterpret_cast(aSrc); uint32x4_t dst32; if (aLength >= 2) { // Load first 2 pixels dst32 = vcombine_u32(vld1_u32(src32), vdup_n_u32(0)); // Load third pixel if (aLength >= 3) { dst32 = vld1q_lane_u32(src32 + 2, dst32, 2); } } else { // Load single pixel dst32 = vld1q_lane_u32(src32, vdupq_n_u32(0), 0); } return vreinterpretq_u16_u32(dst32); } template requires std::same_as || std::same_as static MOZ_ALWAYS_INLINE void StoreRemainder_SIMD( uint8_t* aDst, size_t aLength, const xsimd::batch& aSrc) { uint32_t* dst32 = reinterpret_cast(aDst); uint32x4_t src32 = vreinterpretq_u32_u16(aSrc); if (aLength >= 2) { // Store first 2 pixels vst1_u32(dst32, vget_low_u32(src32)); // Store third pixel if (aLength >= 3) { vst1q_lane_u32(dst32 + 2, src32, 2); } } else { // Store single pixel vst1q_lane_u32(dst32, src32, 0); } } // This matches the original specialization, which is cheaper than what xsimd // and clang generate. template requires std::same_as || std::same_as static MOZ_ALWAYS_INLINE xsimd::batch ExtractAlpha_SIMD( const xsimd::batch& aSrc, const xsimd::batch& aGreenAlpha) { return vtrnq_u16(aGreenAlpha, aGreenAlpha).val[1]; } // arm devices perform better with the original specialization as aarch64, // because there is no xsimd::swizzle vectorization. template requires std::same_as static MOZ_ALWAYS_INLINE xsimd::batch SwizzleVector_SIMD( const xsimd::batch& aSrc) { if constexpr (aSwapRB) { // Swap R and B, then add to G and A (forced to 255): // (((src>>16) | (src << 16)) & 0x00FF00FF) | // ((src | 0xFF000000) & ~0x00FF00FF) return vbslq_u16( vdupq_n_u16(0x00FF), vrev32q_u16(aSrc), aOpaqueAlpha ? vorrq_u16(aSrc, vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000))) : aSrc.data); } if constexpr (aOpaqueAlpha) { return vorrq_u16(aSrc, vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000))); } return aSrc; } template requires std::same_as || std::same_as static MOZ_ALWAYS_INLINE xsimd::batch UnpremultiplyLookup_SIMD( const xsimd::batch& aSrc, const xsimd::batch& aGa) { // Extract the alphas for the 4 pixels from the now isolated words. alignas(Arch::alignment()) uint16_t alphaBuf[xsimd::batch::size]; aGa.store_aligned(alphaBuf); // Load all of the interleaved low and high portions of the reciprocals // and combine them a single vector as lo1 hi1 lo2 hi2 lo3 hi3 lo4 hi4 return vreinterpretq_u16_u32(vld1q_lane_u32( &sUnpremultiplyTable_NEON[alphaBuf[7]], vld1q_lane_u32( &sUnpremultiplyTable_NEON[alphaBuf[5]], vld1q_lane_u32(&sUnpremultiplyTable_NEON[alphaBuf[3]], vld1q_lane_u32(&sUnpremultiplyTable_NEON[alphaBuf[1]], vdupq_n_u32(0), 0), 1), 2), 3)); } template requires std::same_as || std::same_as static MOZ_ALWAYS_INLINE xsimd::batch UnpremultiplyReverse_SIMD( const xsimd::batch& aSrc, const xsimd::batch& aRecip, const xsimd::batch& aRb, const xsimd::batch& aGa) { // Transpose the interleaved low/high portions so that we produce // two separate duplicated vectors for the low and high portions respectively: // lo1 lo1 lo2 lo2 lo3 lo3 lo4 lo4 and hi1 hi1 hi2 hi2 hi3 hi3 hi4 hi4 uint16x8x2_t q1234lohi = vtrnq_u16(aRecip, aRecip); // VQDMULH is a signed multiply that doubles (*2) the result, then takes the // high word. To work around the signedness and the doubling, the low // portion of the reciprocal only stores the lower 15 bits, which fits in a // signed 16 bit integer. The high 9 bit portion is effectively also doubled // by 2 as a side-effect of being shifted for storage. Thus the output scale // of doing a normal multiply by the high portion and the VQDMULH by the low // portion are both doubled and can be safely added together. The resulting // sum just needs to be halved (via VHADD) to thus cancel out the doubling. // All this combines to produce a reciprocal multiply of the form: // rb = ((rb * hi) + ((rb * lo * 2) >> 16)) / 2 auto rb = vhaddq_u16(vmulq_u16(aRb, q1234lohi.val[1]), vreinterpretq_u16_s16(vqdmulhq_s16( vreinterpretq_s16_u16(aRb), vreinterpretq_s16_u16(q1234lohi.val[0])))); // ga = ((ga * hi) + ((ga * lo * 2) >> 16)) / 2 auto ga = vhaddq_u16(vmulq_u16(aGa, q1234lohi.val[1]), vreinterpretq_u16_s16(vqdmulhq_s16( vreinterpretq_s16_u16(aGa), vreinterpretq_s16_u16(q1234lohi.val[0])))); // Combine to the final pixel with ((rb | (ga << 8)) & ~0xFF000000) | (aSrc & // 0xFF000000), which inserts back in the original alpha value unchanged. return vbslq_u16(vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000)), aSrc, vsliq_n_u16(rb, ga, 8)); } template requires std::same_as || std::same_as static MOZ_ALWAYS_INLINE xsimd::batch LoadRemainderRGB_SIMD( const uint8_t* aSrc, size_t aLength) { // Load aLength (1-4) packed RGB pixels (3-12 bytes) without reading past the // 3*aLength valid source bytes. Bytes beyond the loaded pixels are unused by // the expand swizzle and dropped by StoreRemainder_SIMD. uint32x4_t px = vdupq_n_u32(0); if (aLength >= 2) { // Bytes 0-3: pixel 0 (RGB) and pixel 1's R. px = vld1q_lane_u32(reinterpret_cast(aSrc), px, 0); if (aLength >= 3) { // Bytes 4-7: pixel 1's GB and pixel 2's RG. px = vld1q_lane_u32(reinterpret_cast(aSrc + 4), px, 1); if (aLength >= 4) { // Bytes 8-11: pixel 2's B and pixel 3 (RGB). px = vld1q_lane_u32(reinterpret_cast(aSrc + 8), px, 2); } else { // 3 pixels = 9 bytes; byte 8 is pixel 2's B. return vld1q_lane_u8(aSrc + 8, vreinterpretq_u8_u32(px), 8); } } else { // 2 pixels = 6 bytes; bytes 4-5 are pixel 1's GB. return vreinterpretq_u8_u16( vld1q_lane_u16(reinterpret_cast(aSrc + 4), vreinterpretq_u16_u32(px), 2)); } return vreinterpretq_u8_u32(px); } // 1 pixel = 3 bytes: bytes 0-1 then byte 2. uint8x16_t p8 = vreinterpretq_u8_u16(vld1q_lane_u16( reinterpret_cast(aSrc), vreinterpretq_u16_u32(px), 0)); return vld1q_lane_u8(aSrc + 2, p8, 2); } // arm devices perform better with the original specialization as aarch64, // because there is no xsimd::swizzle vectorization. template requires std::same_as static MOZ_ALWAYS_INLINE xsimd::batch UnpackBatchRGB24_SIMD( const xsimd::batch& aSrc) { uint8x16_t alpha = vreinterpretq_u8_u32(vdupq_n_u32(0xFF000000)); uint8x8_t masklo; uint8x8_t maskhi; if constexpr (aSwapRB) { static const uint8_t masklo_data[] = {2, 1, 0, 0, 5, 4, 3, 0}; static const uint8_t maskhi_data[] = {4, 3, 2, 0, 7, 6, 5, 0}; masklo = vld1_u8(masklo_data); maskhi = vld1_u8(maskhi_data); } else { static const uint8_t masklo_data[] = {0, 1, 2, 0, 3, 4, 5, 0}; static const uint8_t maskhi_data[] = {2, 3, 4, 0, 5, 6, 7, 0}; masklo = vld1_u8(masklo_data); maskhi = vld1_u8(maskhi_data); } // G2R2B1G1 R1B0G0R0 -> X1R1G1B1 X0R0G0B0 uint8x8_t pxlo = vtbl1_u8(vget_low_u8(aSrc), masklo); // B3G3R3B2 G2R2B1G1 -> X3R3G3B3 X2R2G2B2 uint8x8_t pxhi = vtbl1_u8(vext_u8(vget_low_u8(aSrc), vget_high_u8(aSrc), 4), maskhi); return vorrq_u8(vcombine_u8(pxlo, pxhi), alpha); } } // namespace mozilla::gfx #endif /* MOZILLA_GFX_SWIZZLE_NEON_H_ */