/* 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/. */ #include #include #include "Orientation.h" #include "gtest/MozGTestBench.h" #include "gtest/gtest.h" #include "mozilla/ArrayUtils.h" #include "mozilla/UniquePtr.h" #include "mozilla/UniquePtrExtensions.h" #include "mozilla/gfx/Swizzle.h" using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::image; namespace mozilla::gfx { // These are implementations of the row methods using SwizzleGeneric.h without // any architecture specific specialization. This allows us to test the generics // without any of the architecture specific template specializations and instead // use only the pure generics. This is useful to test because if a new target // doesn't use any specializations for the actual operations, we want to keep it // working. SwizzleRowFn PremultiplyRowGeneric(SurfaceFormat aSrcFormat, SurfaceFormat aDstFormat, SwizzleArch aArch); SwizzleRowFn UnpremultiplyRowGeneric(SurfaceFormat aSrcFormat, SurfaceFormat aDstFormat, SwizzleArch aArch); SwizzleRowFn SwizzleRowGeneric(SurfaceFormat aSrcFormat, SurfaceFormat aDstFormat, SwizzleArch aArch); } // namespace mozilla::gfx enum class SwizzleOp { Copy, YFlip, YFlipInplace, Premultiply, PremultiplyYFlip, PremultiplyYFlipInplace, Unpremultiply }; // AVX2 is our widest at 32 bytes, or 8 pixels. Below we calculate the value // needed to cover 3 full passes, including any remainders. constexpr int32_t kMaxSweepPixels = 8 * 3; // If the arch isn't available on this machine, the swizzle will just not // happen, and then we will try the next one. constexpr SwizzleArch kSwizzleArchs[] = { SwizzleArch::eAny, SwizzleArch::eFallback, SwizzleArch::eGeneric, #ifdef USE_SSE2 SwizzleArch::eSSE2, SwizzleArch::eSSSE3, SwizzleArch::eAVX2, #endif #ifdef USE_NEON SwizzleArch::eNEON, #endif }; static uint8_t RefPremultiply(uint8_t aColor, uint8_t aAlpha) { uint32_t t = uint32_t(aColor) * aAlpha + 0xFF; return uint8_t((t + (t >> 8)) >> 8); } static uint8_t RefUnpremultiply(uint8_t aColor, uint8_t aAlpha) { uint32_t q = aAlpha ? (0xFF00FFu / aAlpha) : 0u; return uint8_t((uint32_t(aColor) * q) >> 16); } // Generate the expected pixel for testing with the given // configuration/operation. It does this in the simplest way possible for manual // verification of correctness. static void GeneratePixel(SwizzleOp aOp, SurfaceFormat aDstFormat, uint8_t aB, uint8_t aG, uint8_t aR, uint8_t aA, uint8_t* aDst) { uint8_t r = aR; uint8_t g = aG; uint8_t b = aB; switch (aOp) { default: break; case SwizzleOp::Premultiply: case SwizzleOp::PremultiplyYFlip: case SwizzleOp::PremultiplyYFlipInplace: r = RefPremultiply(aR, aA); g = RefPremultiply(aG, aA); b = RefPremultiply(aB, aA); break; case SwizzleOp::Unpremultiply: r = RefUnpremultiply(aR, aA); g = RefUnpremultiply(aG, aA); b = RefUnpremultiply(aB, aA); break; } switch (aDstFormat) { case SurfaceFormat::B8G8R8A8: aDst[0] = b; aDst[1] = g; aDst[2] = r; aDst[3] = aA; break; case SurfaceFormat::R8G8B8A8: aDst[0] = r; aDst[1] = g; aDst[2] = b; aDst[3] = aA; break; case SurfaceFormat::A8R8G8B8: aDst[0] = aA; aDst[1] = r; aDst[2] = g; aDst[3] = b; break; case SurfaceFormat::R8G8B8X8: aDst[0] = r; aDst[1] = g; aDst[2] = b; aDst[3] = 0xFF; break; case SurfaceFormat::B8G8R8X8: aDst[0] = b; aDst[1] = g; aDst[2] = r; aDst[3] = 0xFF; break; case SurfaceFormat::X8R8G8B8: aDst[0] = 0xFF; aDst[1] = r; aDst[2] = g; aDst[3] = b; break; default: ADD_FAILURE() << "Unhandled destination format"; break; } } // Reference CMYK -> RGB conversion matching the scalar SwizzleCmykRowFallback: // R = iC*iK/255, G = iM*iK/255, B = iY*iK/255 (truncating), with i meaning // inverted. The result is packed via GeneratePixel so the destination byte // order is handled consistently. static void GenerateCmykPixel(bool aInverted, SurfaceFormat aDstFormat, uint8_t aC, uint8_t aM, uint8_t aY, uint8_t aK, uint8_t* aDst) { uint32_t iC = aC, iM = aM, iY = aY, iK = aK; // Invert if necessary, as the math expects inverted CMYK. if (!aInverted) { iC = 255 - iC; iM = 255 - iM; iY = 255 - iY; iK = 255 - iK; } uint8_t r = iC * iK / 255; uint8_t g = iM * iK / 255; uint8_t b = iY * iK / 255; GeneratePixel(SwizzleOp::Copy, aDstFormat, b, g, r, 0xFF, aDst); } // Generate a BGRA sample with a range of channel values. It can clamp the RGB // values to ensure it is normal for unpremultiply. static void FillTestBGRA(uint8_t* aDst, int32_t aWidth, bool aClampToAlpha) { for (int32_t i = 0; i < aWidth; ++i) { uint8_t a = (i % 17 == 0) ? 0 : (i % 13 == 0) ? 255 : uint8_t((i * 5 + 7) & 0xFF); uint8_t b = uint8_t((i * 7 + 1) & 0xFF); uint8_t g = uint8_t((i * 13 + 2) & 0xFF); uint8_t r = uint8_t((i * 29 + 3) & 0xFF); if (aClampToAlpha) { b = std::min(b, a); g = std::min(g, a); r = std::min(r, a); } aDst[i * 4 + 0] = b; aDst[i * 4 + 1] = g; aDst[i * 4 + 2] = r; aDst[i * 4 + 3] = a; } } static SwizzleRowFn RowFnFor(SwizzleOp aOp, SurfaceFormat aSrc, SurfaceFormat aDst, SwizzleArch aArch) { switch (aOp) { case SwizzleOp::Premultiply: return PremultiplyRowGeneric(aSrc, aDst, aArch); case SwizzleOp::Unpremultiply: return UnpremultiplyRowGeneric(aSrc, aDst, aArch); case SwizzleOp::Copy: return SwizzleRowGeneric(aSrc, aDst, aArch); default: MOZ_ASSERT_UNREACHABLE("Unhandled row SwizzleOp!"); break; } return nullptr; } // Verify the *Data methods exported for swizzling. static void CheckBGRADataSweep(SwizzleOp aOp, SurfaceFormat aDstFormat) { constexpr int32_t kHeight = 3; constexpr int32_t kGapPixels = 5; constexpr int32_t kStride = (kMaxSweepPixels + kGapPixels) * 4; constexpr int32_t kSize = kStride * kHeight; uint8_t src[kSize]; uint8_t dst[kSize]; uint8_t expected[kSize]; for (int32_t i = 0; i < kHeight; ++i) { uint8_t* rowStart = src + i * kStride; FillTestBGRA(rowStart, kMaxSweepPixels, aOp == SwizzleOp::Unpremultiply); memset(rowStart + kMaxSweepPixels * 4, 0xDF, kGapPixels * 4); } bool inplace = aOp == SwizzleOp::YFlipInplace || aOp == SwizzleOp::PremultiplyYFlipInplace; bool yflip = inplace || aOp == SwizzleOp::YFlip || aOp == SwizzleOp::PremultiplyYFlip; const uint8_t* srcOpPtr = inplace ? dst : src; for (SwizzleArch arch : kSwizzleArchs) { SCOPED_TRACE(testing::Message() << "arch=" << int(arch)); // When we are inplace, there are pixels not just dummy values from src so // we need to copy src into expected first so that the unwritten pixels // still match. if (inplace) { memcpy(expected, src, sizeof(expected)); } else { memset(expected, 0xCD, sizeof(expected)); } for (int32_t i = 0; i < kMaxSweepPixels; ++i) { for (int32_t k = 0; k < kHeight; ++k) { int32_t srcPos = k * kStride + i * 4; int32_t dstPos = yflip ? (kHeight - k - 1) * kStride + i * 4 : srcPos; GeneratePixel(aOp, aDstFormat, src[srcPos], src[srcPos + 1], src[srcPos + 2], src[srcPos + 3], expected + dstPos); } int32_t width = i + 1; if (inplace) { memcpy(dst, src, sizeof(dst)); } else { memset(dst, 0xCD, sizeof(dst)); } bool success = false; switch (aOp) { case SwizzleOp::Copy: success = SwizzleData(srcOpPtr, kStride, SurfaceFormat::B8G8R8A8, dst, kStride, aDstFormat, IntSize(width, kHeight), arch); break; case SwizzleOp::YFlip: case SwizzleOp::YFlipInplace: success = SwizzleYFlipData(srcOpPtr, kStride, SurfaceFormat::B8G8R8A8, dst, kStride, aDstFormat, IntSize(width, kHeight), arch); break; case SwizzleOp::Premultiply: success = PremultiplyData(srcOpPtr, kStride, SurfaceFormat::B8G8R8A8, dst, kStride, aDstFormat, IntSize(width, kHeight), arch); break; case SwizzleOp::PremultiplyYFlip: case SwizzleOp::PremultiplyYFlipInplace: success = PremultiplyYFlipData( srcOpPtr, kStride, SurfaceFormat::B8G8R8A8, dst, kStride, aDstFormat, IntSize(width, kHeight), arch); break; case SwizzleOp::Unpremultiply: success = UnpremultiplyData( srcOpPtr, kStride, SurfaceFormat::B8G8R8A8, dst, kStride, aDstFormat, IntSize(width, kHeight), arch); break; default: MOZ_ASSERT_UNREACHABLE("Unhandled SwizzleOp!"); break; } // We can fail if the arch isn't supported for this operation, but // any/fallback should always succeed. if (!success) { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); break; } EXPECT_TRUE(ArrayEqual(dst, expected)) << (inplace ? "same buffer" : "separate buffers") << ", width " << width; } } } // Verify the *Row methods exported for swizzling. static void CheckBGRARowSweep(SwizzleOp aOp, SurfaceFormat aDstFormat) { constexpr int32_t kSize = kMaxSweepPixels * 4; uint8_t src[kSize]; uint8_t dst[kSize]; uint8_t expected[kSize]; FillTestBGRA(src, kMaxSweepPixels, aOp == SwizzleOp::Unpremultiply); for (SwizzleArch arch : kSwizzleArchs) { SCOPED_TRACE(testing::Message() << "dstFormat=" << int(aDstFormat) << " arch=" << int(arch)); // We can fail if the arch isn't supported for this operation, but // any/fallback should always succeed. SwizzleRowFn func = RowFnFor(aOp, SurfaceFormat::B8G8R8A8, aDstFormat, arch); if (!func) { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); continue; } memset(expected, 0xCD, sizeof(expected)); for (int32_t i = 0; i < kMaxSweepPixels; ++i) { GeneratePixel(aOp, aDstFormat, src[i * 4 + 0], src[i * 4 + 1], src[i * 4 + 2], src[i * 4 + 3], &expected[i * 4]); int32_t len = i + 1; memset(dst, 0xCD, sizeof(dst)); func(src, dst, len); EXPECT_TRUE(ArrayEqual(dst, expected)) << "separate buffers, length " << len; memcpy(dst, src, len * 4); func(dst, dst, len); EXPECT_TRUE(ArrayEqual(dst, expected)) << "in place, length " << len; } } } // Verify the SwizzleRow methods exported for unpacking RGB, which has // different sizes and so needs a slightly different algorithm for generating // the inputs. static void CheckUnpackRowSweep(SurfaceFormat aDstFormat) { constexpr int32_t kSrcSize = kMaxSweepPixels * 3; constexpr int32_t kDstSize = kMaxSweepPixels * 4; uint8_t src[kSrcSize]; uint8_t dst[kDstSize]; uint8_t expected[kDstSize]; for (int32_t i = 0; i < kSrcSize; ++i) { src[i] = uint8_t((i * 11 + 5) & 0xFF); } for (SwizzleArch arch : kSwizzleArchs) { SCOPED_TRACE(testing::Message() << "dstFormat=" << int(aDstFormat) << " arch=" << int(arch)); // We can fail if the arch isn't supported for this operation, but // any/fallback should always succeed. SwizzleRowFn func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::R8G8B8, aDstFormat, arch); if (!func) { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); continue; } memset(expected, 0xCD, sizeof(expected)); for (int32_t i = 0; i < kMaxSweepPixels; ++i) { uint8_t r = src[i * 3 + 0]; uint8_t g = src[i * 3 + 1]; uint8_t b = src[i * 3 + 2]; GeneratePixel(SwizzleOp::Copy, aDstFormat, b, g, r, 0xFF, &expected[i * 4]); int32_t len = i + 1; memset(dst, 0xCD, sizeof(dst)); func(src, dst, len); EXPECT_TRUE(ArrayEqual(dst, expected)) << "unpack separate buffers, length " << len; memcpy(dst, src, len * 3); func(dst, dst, len); EXPECT_TRUE(ArrayEqual(dst, expected)) << "unpack in place, length " << len; } } } // Verify the *Row methods exported for swizzling CMYK. static void CheckCmykRowSweep(SurfaceFormat aSrcFormat) { constexpr int32_t kSize = kMaxSweepPixels * 4; uint8_t src[kSize]; uint8_t dst[kSize]; uint8_t expected[kSize]; // Spread of C/M/Y/K values across the row. The multiplier and addition are // arbitrary values intended to select for values across the spectrum. for (int32_t i = 0; i < kSize; ++i) { src[i] = uint8_t((i * 37 + 11) & 0xFF); } const bool inverted = aSrcFormat == SurfaceFormat::InvertedCMYK; for (SwizzleArch arch : kSwizzleArchs) { SCOPED_TRACE(testing::Message() << "srcFormat=" << int(aSrcFormat) << " arch=" << int(arch)); SwizzleRowFn func = RowFnFor(SwizzleOp::Copy, aSrcFormat, SurfaceFormat::B8G8R8X8, arch); if (!func) { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); continue; } memset(expected, 0xCD, sizeof(expected)); for (int32_t i = 0; i < kMaxSweepPixels; ++i) { GenerateCmykPixel(inverted, SurfaceFormat::B8G8R8X8, src[i * 4 + 0], src[i * 4 + 1], src[i * 4 + 2], src[i * 4 + 3], &expected[i * 4]); int32_t len = i + 1; memset(dst, 0xCD, sizeof(dst)); func(src, dst, len); EXPECT_TRUE(ArrayEqual(dst, expected)) << "separate buffers, length " << len; memcpy(dst, src, len * 4); func(dst, dst, len); EXPECT_TRUE(ArrayEqual(dst, expected)) << "in place, length " << len; } } } TEST(Moz2D, PremultiplyData) { const uint8_t in_bgra[5 * 4] = { 255, 255, 0, 255, // verify 255 alpha leaves RGB unchanged 0, 0, 255, 255, 0, 255, 255, 0, // verify 0 alpha zeroes out RGB 0, 0, 0, 0, 255, 0, 0, 128, // verify that 255 RGB maps to alpha }; uint8_t out[5 * 4]; const uint8_t check_bgra[5 * 4] = { 255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, }; // check swizzled output const uint8_t check_rgba[5 * 4] = { 0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, }; const uint8_t check_argb[5 * 4] = { 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, }; PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_bgra)); PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(in_bgra), SurfaceFormat::R8G8B8A8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_rgba)); PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(in_bgra), SurfaceFormat::A8R8G8B8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_argb)); } TEST(Moz2D, PremultiplyRow) { const uint8_t in_bgra[5 * 4] = { 255, 255, 0, 255, // verify 255 alpha leaves RGB unchanged 0, 0, 255, 255, 0, 255, 255, 0, // verify 0 alpha zeroes out RGB 0, 0, 0, 0, 255, 0, 0, 128, // verify that 255 RGB maps to alpha }; uint8_t out[5 * 4]; const uint8_t check_bgra[5 * 4] = { 255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, }; // check swizzled output const uint8_t check_rgba[5 * 4] = { 0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, }; const uint8_t check_argb[5 * 4] = { 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, }; for (auto arch : kSwizzleArchs) { SwizzleRowFn func = RowFnFor(SwizzleOp::Premultiply, SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8, arch); if (func) { func(in_bgra, out, 5); EXPECT_TRUE(ArrayEqual(out, check_bgra)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Premultiply, SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8, arch); if (func) { func(in_bgra, out, 5); EXPECT_TRUE(ArrayEqual(out, check_rgba)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Premultiply, SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8, arch); if (func) { func(in_bgra, out, 5); EXPECT_TRUE(ArrayEqual(out, check_argb)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } } } TEST(Moz2D, PremultiplyYFlipData) { const uint8_t stride = 2 * 4; const uint8_t in_bgra[6 * 4] = { 255, 255, 0, 255, // row 1: verify 255 alpha leaves RGB unchanged 0, 0, 255, 255, 0, 255, 255, 0, // row 2: verify 0 alpha zeroes out RGB 0, 0, 0, 0, 255, 0, 0, 128, // row 3: verify that 255 RGB maps to alpha 255, 255, 255, 128, }; const uint8_t in_bgra_2[4 * 4] = { 255, 255, 0, 255, // row 1: verify 255 alpha leaves RGB unchanged 0, 0, 255, 255, 0, 255, 255, 0, // row 2: verify 0 alpha zeroes out RGB 0, 0, 0, 0, }; const uint8_t in_bgra_3[2 * 4] = { 255, 0, 0, 128, // row 1: verify that 255 RGB maps to alpha 255, 255, 255, 128, }; uint8_t out[6 * 4]; uint8_t out_2[4 * 4]; uint8_t out_3[2 * 4]; const uint8_t check_bgra[6 * 4] = { 128, 0, 0, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 255, 0, 0, 255, 255, }; const uint8_t check_bgra_2[4 * 4] = { 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 255, 0, 0, 255, 255, }; const uint8_t check_bgra_3[2 * 4] = { 128, 0, 0, 128, 128, 128, 128, 128, }; // check swizzled output const uint8_t check_rgba[6 * 4] = { 0, 0, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, }; // Premultiply. PremultiplyYFlipData(in_bgra, stride, SurfaceFormat::B8G8R8A8, out, stride, SurfaceFormat::B8G8R8A8, IntSize(2, 3)); EXPECT_TRUE(ArrayEqual(out, check_bgra)); // Premultiply in-place with middle row. memcpy(out, in_bgra, sizeof(out)); PremultiplyYFlipData(out, stride, SurfaceFormat::B8G8R8A8, out, stride, SurfaceFormat::B8G8R8A8, IntSize(2, 3)); EXPECT_TRUE(ArrayEqual(out, check_bgra)); // Premultiply in-place without middle row. memcpy(out_2, in_bgra_2, sizeof(out_2)); PremultiplyYFlipData(out_2, stride, SurfaceFormat::B8G8R8A8, out_2, stride, SurfaceFormat::B8G8R8A8, IntSize(2, 2)); EXPECT_TRUE(ArrayEqual(out_2, check_bgra_2)); // Premultiply in-place only middle row. memcpy(out_3, in_bgra_3, sizeof(out_3)); PremultiplyYFlipData(out_3, stride, SurfaceFormat::B8G8R8A8, out_3, stride, SurfaceFormat::B8G8R8A8, IntSize(2, 1)); EXPECT_TRUE(ArrayEqual(out_3, check_bgra_3)); // Premultiply and swizzle with middle row. PremultiplyYFlipData(in_bgra, stride, SurfaceFormat::B8G8R8A8, out, stride, SurfaceFormat::R8G8B8A8, IntSize(2, 3)); EXPECT_TRUE(ArrayEqual(out, check_rgba)); } TEST(Moz2D, UnpremultiplyData) { const uint8_t in_bgra[5 * 4] = { 255, 255, 0, 255, // verify 255 alpha leaves RGB unchanged 0, 0, 255, 255, 0, 0, 0, 0, // verify 0 alpha leaves RGB at 0 0, 0, 0, 64, // verify 0 RGB stays 0 with non-zero alpha 128, 0, 0, 128, // verify that RGB == alpha maps to 255 }; uint8_t out[5 * 4]; const uint8_t check_bgra[5 * 4] = { 255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 64, 255, 0, 0, 128, }; // check swizzled output const uint8_t check_rgba[5 * 4] = { 0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 255, 128, }; const uint8_t check_argb[5 * 4] = { 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 128, 0, 0, 255, }; UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_bgra)); UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(in_bgra), SurfaceFormat::R8G8B8A8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_rgba)); UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(in_bgra), SurfaceFormat::A8R8G8B8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_argb)); } TEST(Moz2D, UnpremultiplyRow) { const uint8_t in_bgra[5 * 4] = { 255, 255, 0, 255, // verify 255 alpha leaves RGB unchanged 0, 0, 255, 255, 0, 0, 0, 0, // verify 0 alpha leaves RGB at 0 0, 0, 0, 64, // verify 0 RGB stays 0 with non-zero alpha 128, 0, 0, 128, // verify that RGB == alpha maps to 255 }; uint8_t out[5 * 4]; const uint8_t check_bgra[5 * 4] = { 255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 64, 255, 0, 0, 128, }; // check swizzled output const uint8_t check_rgba[5 * 4] = { 0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 255, 128, }; const uint8_t check_argb[5 * 4] = { 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 128, 0, 0, 255, }; for (auto arch : kSwizzleArchs) { SwizzleRowFn func = RowFnFor(SwizzleOp::Unpremultiply, SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8, arch); if (func) { func(in_bgra, out, 5); EXPECT_TRUE(ArrayEqual(out, check_bgra)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Unpremultiply, SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8, arch); if (func) { func(in_bgra, out, 5); EXPECT_TRUE(ArrayEqual(out, check_rgba)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Unpremultiply, SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8, arch); if (func) { func(in_bgra, out, 5); EXPECT_TRUE(ArrayEqual(out, check_argb)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } } } TEST(Moz2D, SwizzleData) { const uint8_t in_bgra[5 * 4] = { 253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 1, 2, 3, 64, 127, 0, 9, 128, }; uint8_t out[5 * 4]; // check copy const uint8_t check_bgra[5 * 4] = { 253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 1, 2, 3, 64, 127, 0, 9, 128, }; // check swaps const uint8_t check_rgba[5 * 4] = { 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 0, 3, 2, 1, 64, 9, 0, 127, 128, }; const uint8_t check_argb[5 * 4] = { 255, 0, 254, 253, 255, 255, 0, 0, 0, 0, 0, 0, 64, 3, 2, 1, 128, 9, 0, 127, }; // check opaquifying const uint8_t check_rgbx[5 * 4] = { 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 255, 3, 2, 1, 255, 9, 0, 127, 255, }; // check packing uint8_t out24[5 * 3]; const uint8_t check_bgr[5 * 3] = {253, 254, 0, 0, 0, 255, 0, 0, 0, 1, 2, 3, 127, 0, 9}; const uint8_t check_rgb[5 * 3] = { 0, 254, 253, 255, 0, 0, 0, 0, 0, 3, 2, 1, 9, 0, 127, }; uint8_t out8[5]; const uint8_t check_a[5] = {255, 255, 0, 64, 128}; uint16_t out16[5]; #define PACK_RGB565(b, g, r) \ (((b & 0xF8) >> 3) | ((g & 0xFC) << 3) | ((r & 0xF8) << 8)) const uint16_t check_16[5] = { PACK_RGB565(253, 254, 0), PACK_RGB565(0, 0, 255), PACK_RGB565(0, 0, 0), PACK_RGB565(1, 2, 3), PACK_RGB565(127, 0, 9), }; SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(out), SurfaceFormat::B8G8R8A8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_bgra)); SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(out), SurfaceFormat::R8G8B8A8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_rgba)); SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(out), SurfaceFormat::A8R8G8B8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_argb)); SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, sizeof(out), SurfaceFormat::R8G8B8X8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out, check_rgbx)); SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out24, sizeof(out24), SurfaceFormat::B8G8R8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out24, check_bgr)); SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out24, sizeof(out24), SurfaceFormat::R8G8B8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out24, check_rgb)); SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out8, sizeof(out8), SurfaceFormat::A8, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out8, check_a)); const uint8_t* uint32_argb; if constexpr (std::endian::native == std::endian::big) { EXPECT_EQ(SurfaceFormat::A8R8G8B8_UINT32, SurfaceFormat::A8R8G8B8); uint32_argb = check_argb; } else { EXPECT_EQ(SurfaceFormat::A8R8G8B8_UINT32, SurfaceFormat::B8G8R8A8); uint32_argb = check_bgra; } SwizzleData(uint32_argb, sizeof(in_bgra), SurfaceFormat::A8R8G8B8_UINT32, reinterpret_cast(out16), sizeof(out16), SurfaceFormat::R5G6B5_UINT16, IntSize(5, 1)); EXPECT_TRUE(ArrayEqual(out16, check_16)); } TEST(Moz2D, SwizzleYFlipData) { const uint8_t stride = 2 * 4; const uint8_t in_bgra[6 * 4] = { 255, 255, 0, 255, // row 1 0, 0, 255, 255, 0, 255, 255, 0, // row 2 0, 0, 0, 0, 255, 0, 0, 128, // row 3 255, 255, 255, 128, }; const uint8_t in_bgra_2[4 * 4] = { 255, 255, 0, 255, // row 1 0, 0, 255, 255, 0, 255, 255, 0, // row 2 0, 0, 0, 0, }; const uint8_t in_bgra_3[2 * 4] = { 255, 0, 0, 128, // row 1 255, 255, 255, 128, }; uint8_t out[6 * 4]; uint8_t out_2[4 * 4]; uint8_t out_3[2 * 4]; const uint8_t check_rgba[6 * 4] = { 0, 0, 255, 128, 255, 255, 255, 128, 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, }; const uint8_t check_rgba_2[4 * 4] = { 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, }; const uint8_t check_rgba_3[2 * 4] = { 0, 0, 255, 128, 255, 255, 255, 128, }; // Swizzle. SwizzleYFlipData(in_bgra, stride, SurfaceFormat::B8G8R8A8, out, stride, SurfaceFormat::R8G8B8A8, IntSize(2, 3)); EXPECT_TRUE(ArrayEqual(out, check_rgba)); // Swizzle in-place with middle row. memcpy(out, in_bgra, sizeof(out)); SwizzleYFlipData(out, stride, SurfaceFormat::B8G8R8A8, out, stride, SurfaceFormat::R8G8B8A8, IntSize(2, 3)); EXPECT_TRUE(ArrayEqual(out, check_rgba)); // Swizzle in-place without middle row. memcpy(out_2, in_bgra_2, sizeof(out_2)); SwizzleYFlipData(out_2, stride, SurfaceFormat::B8G8R8A8, out_2, stride, SurfaceFormat::R8G8B8A8, IntSize(2, 2)); EXPECT_TRUE(ArrayEqual(out_2, check_rgba_2)); // Swizzle in-place only middle row. memcpy(out_3, in_bgra_3, sizeof(out_3)); SwizzleYFlipData(out_3, stride, SurfaceFormat::B8G8R8A8, out_3, stride, SurfaceFormat::R8G8B8A8, IntSize(2, 1)); EXPECT_TRUE(ArrayEqual(out_3, check_rgba_3)); } TEST(Moz2D, SwizzleRow) { const uint8_t in_bgra[5 * 4] = { 253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 1, 2, 3, 64, 127, 0, 9, 128, }; uint8_t out[5 * 4]; // check swaps const uint8_t check_rgba[5 * 4] = { 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 0, 3, 2, 1, 64, 9, 0, 127, 128, }; // check opaquifying const uint8_t check_rgbx[5 * 4] = { 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 255, 3, 2, 1, 255, 9, 0, 127, 255, }; // check packing uint8_t out24[5 * 3]; const uint8_t check_bgr[5 * 3] = {253, 254, 0, 0, 0, 255, 0, 0, 0, 1, 2, 3, 127, 0, 9}; const uint8_t check_rgb[5 * 3] = { 0, 254, 253, 255, 0, 0, 0, 0, 0, 3, 2, 1, 9, 0, 127, }; // check unpacking uint8_t out_unpack[16 * 4]; const uint8_t in_rgb[16 * 3] = { 0, 254, 253, 255, 0, 0, 0, 0, 0, 3, 2, 1, 9, 0, 127, 4, 5, 6, 9, 8, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, }; const uint8_t check_unpack_rgbx[16 * 4] = { 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 255, 3, 2, 1, 255, 9, 0, 127, 255, 4, 5, 6, 255, 9, 8, 7, 255, 10, 11, 12, 255, 13, 14, 15, 255, 16, 17, 18, 255, 19, 20, 21, 255, 22, 23, 24, 255, 25, 26, 27, 255, 28, 29, 30, 255, 31, 32, 33, 255, 34, 35, 36, 255, }; const uint8_t check_unpack_bgrx[16 * 4] = { 253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 255, 1, 2, 3, 255, 127, 0, 9, 255, 6, 5, 4, 255, 7, 8, 9, 255, 12, 11, 10, 255, 15, 14, 13, 255, 18, 17, 16, 255, 21, 20, 19, 255, 24, 23, 22, 255, 27, 26, 25, 255, 30, 29, 28, 255, 33, 32, 31, 255, 36, 35, 34, 255, }; const uint8_t check_unpack_xrgb[16 * 4] = { 255, 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 255, 3, 2, 1, 255, 9, 0, 127, 255, 4, 5, 6, 255, 9, 8, 7, 255, 10, 11, 12, 255, 13, 14, 15, 255, 16, 17, 18, 255, 19, 20, 21, 255, 22, 23, 24, 255, 25, 26, 27, 255, 28, 29, 30, 255, 31, 32, 33, 255, 34, 35, 36, }; for (auto arch : kSwizzleArchs) { SwizzleRowFn func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8, arch); if (func) { func(in_bgra, out, 5); EXPECT_TRUE(ArrayEqual(out, check_rgba)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8, arch); if (func) { func(in_bgra, out, 5); EXPECT_TRUE(ArrayEqual(out, check_rgbx)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8, arch); if (func) { func(in_bgra, out, 5); EXPECT_TRUE(ArrayEqual(out, in_bgra)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8, arch); if (func) { func(in_bgra, out24, 5); EXPECT_TRUE(ArrayEqual(out24, check_bgr)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8, arch); if (func) { func(in_bgra, out24, 5); EXPECT_TRUE(ArrayEqual(out24, check_rgb)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8X8, arch); if (func) { func(in_rgb, out_unpack, 16); EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_bgrx)); memset(out_unpack, 0xE5, sizeof(out_unpack)); memcpy(out_unpack, in_rgb, sizeof(in_rgb)); func(out_unpack, out_unpack, 16); EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_bgrx)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::R8G8B8, SurfaceFormat::R8G8B8X8, arch); if (func) { func(in_rgb, out_unpack, 16); EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_rgbx)); memset(out_unpack, 0xE5, sizeof(out_unpack)); memcpy(out_unpack, in_rgb, sizeof(in_rgb)); func(out_unpack, out_unpack, 16); EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_rgbx)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::R8G8B8, SurfaceFormat::X8R8G8B8, arch); if (func) { func(in_rgb, out_unpack, 16); EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_xrgb)); memset(out_unpack, 0xE5, sizeof(out_unpack)); memcpy(out_unpack, in_rgb, sizeof(in_rgb)); func(out_unpack, out_unpack, 16); EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_xrgb)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } } } TEST(Moz2D, SwizzleRowCmyk) { // We use inverted CMYK here as it's preferred in the field and is easier to // explain const uint8_t in_inverted_cmyk[10 * 4] = { 200, 50, 10, 255, // K=255: color passes through (R=iC, G=iM, B=iY) 200, 50, 10, 0, // K=0: fully black 255, 255, 255, 255, // all max -> 255,255,255 0, 0, 0, 0, // all min -> 0,0,0 128, 128, 128, 128, // floor edge: 128*128/255 = 64.25 -> 64 255, 0, 0, 255, // only iC -> only R (catches R/B swap) 0, 0, 255, 255, // only iY -> only B (catches R/B swap) 2, 2, 2, 128, // truncation: 2*128/255 = 1.003 -> 1 255, 255, 255, 1, // tiny K: 255*1/255 = 1 100, 150, 200, 77, // arbitrary mid values }; uint8_t out[10 * 4]; // clang-format off const uint8_t check_inverted_bgrx[10 * 4] = { 10, 50, 200, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 64, 64, 64, 255, 0, 0, 255, 255, 255, 0, 0, 255, 1, 1, 1, 255, 1, 1, 1, 255, 60, 45, 30, 255, }; // Non-inverted CMYK: each channel is inverted (255 - x) during the conversion. const uint8_t check_bgrx[10 * 4] = { 0, 0, 0, 255, 245, 205, 55, 255, 0, 0, 0, 255, 255, 255, 255, 255, 63, 63, 63, 255, 0, 0, 0, 255, 0, 0, 0, 255, 126, 126, 126, 255, 0, 0, 0, 255, 38, 73, 108, 255, }; // clang-format on for (SwizzleArch arch : kSwizzleArchs) { SwizzleRowFn func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::CMYK, SurfaceFormat::B8G8R8X8, arch); if (func) { func(in_inverted_cmyk, out, 10); EXPECT_TRUE(ArrayEqual(out, check_bgrx)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } func = RowFnFor(SwizzleOp::Copy, SurfaceFormat::InvertedCMYK, SurfaceFormat::B8G8R8X8, arch); if (func) { func(in_inverted_cmyk, out, 10); EXPECT_TRUE(ArrayEqual(out, check_inverted_bgrx)); } else { EXPECT_NE(arch, SwizzleArch::eAny); EXPECT_NE(arch, SwizzleArch::eFallback); } } } TEST(Moz2D, SwizzleCmykRowSweep) { CheckCmykRowSweep(SurfaceFormat::CMYK); CheckCmykRowSweep(SurfaceFormat::InvertedCMYK); } TEST(Moz2D, ReorientRow) { // Input is a 3x4 image. const uint8_t in_row0[3 * 4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, }; const uint8_t in_row1[3 * 4] = { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, }; const uint8_t in_row2[3 * 4] = { 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, }; const uint8_t in_row3[3 * 4] = { 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, }; // Output is either a 3x4 image or 4x3 image. uint8_t out[3 * 4 * 4]; IntSize outSize(3, 4); IntSize outSizeSwap(4, 3); int32_t outStride = 3 * 4; int32_t outStrideSwap = 4 * 4; IntRect dirty; auto func = ReorientRow(Orientation()); dirty = func(in_row0, 0, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); dirty = func(in_row1, 1, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); dirty = func(in_row2, 2, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); dirty = func(in_row3, 3, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); // clang-format off const uint8_t check_identity[3 * 4 * 4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, }; // clang-format on EXPECT_TRUE(ArrayEqual(out, check_identity)); func = ReorientRow(Orientation(Angle::D90)); dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); // clang-format off const uint8_t check_d90[3 * 4 * 4] = { 36, 37, 38, 39, 24, 25, 26, 27, 12, 13, 14, 15, 0, 1, 2, 3, 40, 41, 42, 43, 28, 29, 30, 31, 16, 17, 18, 19, 4, 5, 6, 7, 44, 45, 46, 47, 32, 33, 34, 35, 20, 21, 22, 23, 8, 9, 10, 11, }; // clang-format on EXPECT_TRUE(ArrayEqual(out, check_d90)); func = ReorientRow(Orientation(Angle::D180)); dirty = func(in_row0, 0, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); dirty = func(in_row1, 1, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); dirty = func(in_row2, 2, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); dirty = func(in_row3, 3, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); // clang-format off const uint8_t check_d180[3 * 4 * 4] = { 44, 45, 46, 47, 40, 41, 42, 43, 36, 37, 38, 39, 32, 33, 34, 35, 28, 29, 30, 31, 24, 25, 26, 27, 20, 21, 22, 23, 16, 17, 18, 19, 12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3, }; // clang-format on EXPECT_TRUE(ArrayEqual(out, check_d180)); func = ReorientRow(Orientation(Angle::D270)); dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); // clang-format off const uint8_t check_d270[3 * 4 * 4] = { 8, 9, 10, 11, 20, 21, 22, 23, 32, 33, 34, 35, 44, 45, 46, 47, 4, 5, 6, 7, 16, 17, 18, 19, 28, 29, 30, 31, 40, 41, 42, 43, 0, 1, 2, 3, 12, 13, 14, 15, 24, 25, 26, 27, 36, 37, 38, 39, }; // clang-format on EXPECT_TRUE(ArrayEqual(out, check_d270)); func = ReorientRow(Orientation(Angle::D0, Flip::Horizontal)); dirty = func(in_row0, 0, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); dirty = func(in_row1, 1, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); dirty = func(in_row2, 2, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); dirty = func(in_row3, 3, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); // clang-format off const uint8_t check_d0_flip[3 * 4 * 4] = { 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3, 20, 21, 22, 23, 16, 17, 18, 19, 12, 13, 14, 15, 32, 33, 34, 35, 28, 29, 30, 31, 24, 25, 26, 27, 44, 45, 46, 47, 40, 41, 42, 43, 36, 37, 38, 39, }; // clang-format on EXPECT_TRUE(ArrayEqual(out, check_d0_flip)); func = ReorientRow(Orientation(Angle::D90, Flip::Horizontal)); dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); // clang-format off const uint8_t check_d90_flip[3 * 4 * 4] = { 0, 1, 2, 3, 12, 13, 14, 15, 24, 25, 26, 27, 36, 37, 38, 39, 4, 5, 6, 7, 16, 17, 18, 19, 28, 29, 30, 31, 40, 41, 42, 43, 8, 9, 10, 11, 20, 21, 22, 23, 32, 33, 34, 35, 44, 45, 46, 47, }; // clang-format on EXPECT_TRUE(ArrayEqual(out, check_d90_flip)); func = ReorientRow(Orientation(Angle::D180, Flip::Horizontal)); dirty = func(in_row0, 0, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); dirty = func(in_row1, 1, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); dirty = func(in_row2, 2, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); dirty = func(in_row3, 3, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); // clang-format off const uint8_t check_d180_flip[3 * 4 * 4] = { 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, }; // clang-format on EXPECT_TRUE(ArrayEqual(out, check_d180_flip)); func = ReorientRow(Orientation(Angle::D270, Flip::Horizontal)); dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); // clang-format off const uint8_t check_d270_flip[3 * 4 * 4] = { 44, 45, 46, 47, 32, 33, 34, 35, 20, 21, 22, 23, 8, 9, 10, 11, 40, 41, 42, 43, 28, 29, 30, 31, 16, 17, 18, 19, 4, 5, 6, 7, 36, 37, 38, 39, 24, 25, 26, 27, 12, 13, 14, 15, 0, 1, 2, 3, }; // clang-format on EXPECT_TRUE(ArrayEqual(out, check_d270_flip)); func = ReorientRow( Orientation(Angle::D0, Flip::Horizontal, /* aFlipFirst */ true)); dirty = func(in_row0, 0, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); dirty = func(in_row1, 1, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); dirty = func(in_row2, 2, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); dirty = func(in_row3, 3, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); // No rotation, so flipping before and after are the same. EXPECT_TRUE(ArrayEqual(out, check_d0_flip)); func = ReorientRow( Orientation(Angle::D90, Flip::Horizontal, /* aFlipFirst */ true)); dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); // Flip, rotate 90 degrees is the same as rotate 270 degrees, flip. EXPECT_TRUE(ArrayEqual(out, check_d270_flip)); func = ReorientRow( Orientation(Angle::D180, Flip::Horizontal, /* aFlipFirst */ true)); dirty = func(in_row0, 0, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); dirty = func(in_row1, 1, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); dirty = func(in_row2, 2, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); dirty = func(in_row3, 3, out, outSize, outStride); EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); // Flip, rotate 180 degrees is the same as rotate 180 degrees, flip. EXPECT_TRUE(ArrayEqual(out, check_d180_flip)); func = ReorientRow( Orientation(Angle::D270, Flip::Horizontal, /* aFlipFirst */ true)); dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); // Flip, rotate 270 degrees is the same as rotate 90 degrees, flip. EXPECT_TRUE(ArrayEqual(out, check_d90_flip)); } TEST(Moz2D, PremultiplyRowSweep) { CheckBGRARowSweep(SwizzleOp::Premultiply, SurfaceFormat::B8G8R8A8); CheckBGRARowSweep(SwizzleOp::Premultiply, SurfaceFormat::R8G8B8A8); CheckBGRARowSweep(SwizzleOp::Premultiply, SurfaceFormat::A8R8G8B8); } TEST(Moz2D, PremultiplyDataSweep) { CheckBGRADataSweep(SwizzleOp::Premultiply, SurfaceFormat::B8G8R8A8); CheckBGRADataSweep(SwizzleOp::Premultiply, SurfaceFormat::R8G8B8A8); } TEST(Moz2D, PremultiplyYFlipDataSweep) { CheckBGRADataSweep(SwizzleOp::PremultiplyYFlip, SurfaceFormat::B8G8R8A8); CheckBGRADataSweep(SwizzleOp::PremultiplyYFlip, SurfaceFormat::R8G8B8A8); } TEST(Moz2D, PremultiplyYFlipInplaceDataSweep) { CheckBGRADataSweep(SwizzleOp::PremultiplyYFlipInplace, SurfaceFormat::B8G8R8A8); CheckBGRADataSweep(SwizzleOp::PremultiplyYFlipInplace, SurfaceFormat::R8G8B8A8); } TEST(Moz2D, UnpremultiplyRowSweep) { CheckBGRARowSweep(SwizzleOp::Unpremultiply, SurfaceFormat::B8G8R8A8); CheckBGRARowSweep(SwizzleOp::Unpremultiply, SurfaceFormat::R8G8B8A8); CheckBGRARowSweep(SwizzleOp::Unpremultiply, SurfaceFormat::A8R8G8B8); } TEST(Moz2D, UnpremultiplyDataSweep) { CheckBGRADataSweep(SwizzleOp::Unpremultiply, SurfaceFormat::B8G8R8A8); CheckBGRADataSweep(SwizzleOp::Unpremultiply, SurfaceFormat::R8G8B8A8); } TEST(Moz2D, SwizzleDataSweep) { CheckBGRADataSweep(SwizzleOp::Copy, SurfaceFormat::B8G8R8X8); CheckBGRADataSweep(SwizzleOp::Copy, SurfaceFormat::R8G8B8A8); CheckBGRADataSweep(SwizzleOp::Copy, SurfaceFormat::R8G8B8X8); } TEST(Moz2D, SwizzleYFlipDataSweep) { CheckBGRADataSweep(SwizzleOp::YFlip, SurfaceFormat::B8G8R8X8); CheckBGRADataSweep(SwizzleOp::YFlip, SurfaceFormat::R8G8B8A8); CheckBGRADataSweep(SwizzleOp::YFlip, SurfaceFormat::R8G8B8X8); } TEST(Moz2D, SwizzleYFlipInplaceDataSweep) { CheckBGRADataSweep(SwizzleOp::YFlipInplace, SurfaceFormat::B8G8R8X8); CheckBGRADataSweep(SwizzleOp::YFlipInplace, SurfaceFormat::R8G8B8A8); CheckBGRADataSweep(SwizzleOp::YFlipInplace, SurfaceFormat::R8G8B8X8); } TEST(Moz2D, SwizzleRowSweep) { CheckBGRARowSweep(SwizzleOp::Copy, SurfaceFormat::B8G8R8A8); CheckBGRARowSweep(SwizzleOp::Copy, SurfaceFormat::R8G8B8A8); CheckBGRARowSweep(SwizzleOp::Copy, SurfaceFormat::R8G8B8X8); } TEST(Moz2D, UnpackRowRGB24Sweep) { CheckUnpackRowSweep(SurfaceFormat::R8G8B8X8); CheckUnpackRowSweep(SurfaceFormat::B8G8R8X8); CheckUnpackRowSweep(SurfaceFormat::X8R8G8B8); } class Moz2D_SwizzleBench : public ::testing::Test { public: static constexpr int32_t kWidth = 256; static constexpr int32_t kGap = 0; static constexpr int32_t kStride = kWidth * 4 + kGap; static constexpr int32_t kHeight = 4; static constexpr int32_t kBufLen = kStride * kHeight; static constexpr int32_t kRepeat = 256; Moz2D_SwizzleBench() = default; ~Moz2D_SwizzleBench() = default; void SetUp() final { mSrc = MakeUniqueFallible(kBufLen); if (!mSrc) { return; } mDst = MakeUniqueFallible(kBufLen); if (!mDst) { mSrc.reset(); return; } memset(mSrc.get(), 0x2A, kBufLen); memset(mDst.get(), 0x0, kBufLen); } bool SwizzleRow(SwizzleOp aOp, SurfaceFormat aSrcFormat, SurfaceFormat aDstFormat, SwizzleArch aArch) { if (!mSrc || !mDst) { return false; } SwizzleRowFn func = RowFnFor(aOp, aSrcFormat, aDstFormat, aArch); if (!func) { return false; } int32_t srcStride = BytesPerPixel(aSrcFormat) * kWidth + kGap; int32_t dstStride = BytesPerPixel(aDstFormat) * kWidth + kGap; MOZ_ASSERT(srcStride <= kStride); MOZ_ASSERT(dstStride <= kStride); for (int32_t i = 0; i < kRepeat; ++i) { uint8_t* src = mSrc.get(); uint8_t* dst = mDst.get(); for (int32_t k = 0; k < kHeight; ++k) { func(src, dst, kWidth); src += srcStride; dst += dstStride; } } return true; } bool SwizzleData(SwizzleOp aOp, SurfaceFormat aSrcFormat, SurfaceFormat aDstFormat, SwizzleArch aArch) { if (!mSrc || !mDst) { return false; } int32_t srcStride = BytesPerPixel(aSrcFormat) * kWidth + kGap; int32_t dstStride = BytesPerPixel(aDstFormat) * kWidth + kGap; MOZ_ASSERT(srcStride <= kStride); MOZ_ASSERT(dstStride <= kStride); bool success = true; uint8_t* src = mSrc.get(); uint8_t* dst = mDst.get(); for (int32_t i = 0; i < kRepeat && success; ++i) { switch (aOp) { case SwizzleOp::Copy: success = ::SwizzleData(src, srcStride, aSrcFormat, dst, dstStride, aDstFormat, IntSize(kWidth, kHeight), aArch); break; case SwizzleOp::Premultiply: success = PremultiplyData(src, srcStride, aSrcFormat, dst, dstStride, aDstFormat, IntSize(kWidth, kHeight), aArch); break; case SwizzleOp::Unpremultiply: success = UnpremultiplyData(src, srcStride, aSrcFormat, dst, dstStride, aDstFormat, IntSize(kWidth, kHeight), aArch); break; default: MOZ_ASSERT_UNREACHABLE("Unhandled SwizzleOp!"); return false; } } return success; } void TearDown() final { mSrc.reset(); mDst.reset(); } private: UniquePtr mSrc; UniquePtr mDst; }; MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpack_RGB_BGRX_Fallback, [this]() -> bool { return SwizzleRow(SwizzleOp::Copy, SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8X8, SwizzleArch::eFallback); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpack_RGB_BGRX_Generic, [this]() -> bool { return SwizzleRow(SwizzleOp::Copy, SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8X8, SwizzleArch::eGeneric); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpack_RGB_BGRX_NEON, [this]() -> bool { return SwizzleRow(SwizzleOp::Copy, SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8X8, SwizzleArch::eNEON); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpack_RGB_BGRX_SSSE3, [this]() -> bool { return SwizzleRow(SwizzleOp::Copy, SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8X8, SwizzleArch::eSSSE3); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpack_RGB_BGRX_AVX2, [this]() -> bool { return SwizzleRow(SwizzleOp::Copy, SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8X8, SwizzleArch::eAVX2); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_RGBA_BGRX_Fallback, [this]() -> bool { return SwizzleData(SwizzleOp::Copy, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8, SwizzleArch::eFallback); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_RGBA_BGRX_Generic, [this]() -> bool { return SwizzleRow(SwizzleOp::Copy, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8, SwizzleArch::eGeneric); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_RGBA_BGRX_NEON, [this]() -> bool { return SwizzleData(SwizzleOp::Copy, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8, SwizzleArch::eNEON); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_RGBA_BGRX_SSE2, [this]() -> bool { return SwizzleData(SwizzleOp::Copy, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8, SwizzleArch::eSSE2); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_RGBA_BGRX_SSSE3, [this]() -> bool { return SwizzleData(SwizzleOp::Copy, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8, SwizzleArch::eSSSE3); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_RGBA_BGRX_AVX2, [this]() -> bool { return SwizzleData(SwizzleOp::Copy, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8, SwizzleArch::eAVX2); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_InvertedCMYK_BGRX_Fallback, [this]() -> bool { return SwizzleRow( SwizzleOp::Copy, SurfaceFormat::InvertedCMYK, SurfaceFormat::B8G8R8X8, SwizzleArch::eFallback); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_InvertedCMYK_BGRX_Generic, [this]() -> bool { return SwizzleRow( SwizzleOp::Copy, SurfaceFormat::InvertedCMYK, SurfaceFormat::B8G8R8X8, SwizzleArch::eGeneric); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_InvertedCMYK_BGRX_NEON, [this]() -> bool { return SwizzleRow( SwizzleOp::Copy, SurfaceFormat::InvertedCMYK, SurfaceFormat::B8G8R8X8, SwizzleArch::eNEON); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_InvertedCMYK_BGRX_SSE2, [this]() -> bool { return SwizzleRow( SwizzleOp::Copy, SurfaceFormat::InvertedCMYK, SurfaceFormat::B8G8R8X8, SwizzleArch::eSSE2); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Swizzle_InvertedCMYK_BGRX_AVX2, [this]() -> bool { return SwizzleRow( SwizzleOp::Copy, SurfaceFormat::InvertedCMYK, SurfaceFormat::B8G8R8X8, SwizzleArch::eAVX2); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Premultiply_RGBA_BGRA_Fallback, [this]() -> bool { return SwizzleData( SwizzleOp::Premultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eFallback); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Premultiply_RGBA_BGRA_Generic, [this]() -> bool { return SwizzleRow( SwizzleOp::Premultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eGeneric); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Premultiply_RGBA_BGRA_NEON, [this]() -> bool { return SwizzleData( SwizzleOp::Premultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eNEON); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Premultiply_RGBA_BGRA_SSE2, [this]() -> bool { return SwizzleData( SwizzleOp::Premultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eSSE2); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Premultiply_RGBA_BGRA_AVX2, [this]() -> bool { return SwizzleData( SwizzleOp::Premultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eAVX2); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpremultiply_RGBA_BGRA_Fallback, [this]() -> bool { return SwizzleData( SwizzleOp::Unpremultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eFallback); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpremultiply_RGBA_BGRA_Generic, [this]() -> bool { return SwizzleRow( SwizzleOp::Unpremultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eGeneric); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpremultiply_RGBA_BGRA_NEON, [this]() -> bool { return SwizzleData( SwizzleOp::Unpremultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eNEON); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpremultiply_RGBA_BGRA_SSE2, [this]() -> bool { return SwizzleData( SwizzleOp::Unpremultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eSSE2); }); MOZ_GTEST_BENCH_F(Moz2D_SwizzleBench, Unpremultiply_RGBA_BGRA_AVX2, [this]() -> bool { return SwizzleData( SwizzleOp::Unpremultiply, SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8, SwizzleArch::eAVX2); });