From cd66a17327877879c0c06eec4ef2af09eab735c4 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 25 Oct 2023 19:01:38 +0000 Subject: [PATCH 01/10] Issue #2354 - Restore ARM32 buildability on Linux. Fix ION breakages due to BigInt changes. Fix breakages from libjpeg, libyuv and libvpx upgrades. Restore ARM(32/64) NEON FPU support in libyuv. --- js/src/jit/arm/CodeGenerator-arm.h | 18 ------------------ js/src/jit/arm/MacroAssembler-arm.cpp | 4 ++-- js/src/jit/arm/SharedIC-arm.cpp | 11 +++++++++++ media/libjpeg/moz.build | 1 + media/libyuv/libyuv.gyp | 6 +++--- media/libyuv/moz.build | 18 +++++++++++++++++- old-configure.in | 2 +- 7 files changed, 35 insertions(+), 25 deletions(-) diff --git a/js/src/jit/arm/CodeGenerator-arm.h b/js/src/jit/arm/CodeGenerator-arm.h index 02fc40cb26..388ec23df7 100644 --- a/js/src/jit/arm/CodeGenerator-arm.h +++ b/js/src/jit/arm/CodeGenerator-arm.h @@ -288,24 +288,6 @@ class CodeGeneratorARM : public CodeGeneratorShared void visitUDiv(LUDiv* ins); void visitUMod(LUMod* ins); void visitSoftUDivOrMod(LSoftUDivOrMod* ins); - - public: - // Unimplemented SIMD instructions - void visitSimdSplatX4(LSimdSplatX4* lir) { MOZ_CRASH("NYI"); } - void visitSimd128Int(LSimd128Int* ins) { MOZ_CRASH("NYI"); } - void visitSimd128Float(LSimd128Float* ins) { MOZ_CRASH("NYI"); } - void visitSimdReinterpretCast(LSimdReinterpretCast* ins) { MOZ_CRASH("NYI"); } - void visitSimdExtractElementI(LSimdExtractElementI* ins) { MOZ_CRASH("NYI"); } - void visitSimdExtractElementF(LSimdExtractElementF* ins) { MOZ_CRASH("NYI"); } - void visitSimdGeneralShuffleI(LSimdGeneralShuffleI* lir) { MOZ_CRASH("NYI"); } - void visitSimdGeneralShuffleF(LSimdGeneralShuffleF* lir) { MOZ_CRASH("NYI"); } - void visitSimdSwizzleI(LSimdSwizzleI* lir) { MOZ_CRASH("NYI"); } - void visitSimdSwizzleF(LSimdSwizzleF* lir) { MOZ_CRASH("NYI"); } - void visitSimdBinaryCompIx4(LSimdBinaryCompIx4* lir) { MOZ_CRASH("NYI"); } - void visitSimdBinaryCompFx4(LSimdBinaryCompFx4* lir) { MOZ_CRASH("NYI"); } - void visitSimdBinaryArithIx4(LSimdBinaryArithIx4* lir) { MOZ_CRASH("NYI"); } - void visitSimdBinaryArithFx4(LSimdBinaryArithFx4* lir) { MOZ_CRASH("NYI"); } - void visitSimdBinaryBitwise(LSimdBinaryBitwise* lir) { MOZ_CRASH("NYI"); } }; typedef CodeGeneratorARM CodeGeneratorSpecific; diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index e099022c27..cf00f12fb6 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -3013,7 +3013,7 @@ MacroAssemblerARMCompat::testInt32(Condition cond, const BaseIndex& src) return cond; } -+Assembler::Condition +Assembler::Condition MacroAssemblerARMCompat::testBigInt(Condition cond,const BaseIndex& src) { MOZ_ASSERT(cond == Equal || cond == NotEqual); @@ -3766,7 +3766,7 @@ MacroAssemblerARMCompat::testStringTruthy(bool truthy, const ValueOperand& value return truthy ? Assembler::NotEqual : Assembler::Equal; } -+Assembler::Condition +Assembler::Condition MacroAssemblerARMCompat::testBigIntTruthy(bool truthy, const ValueOperand& value) { Register bi = value.payloadReg(); diff --git a/js/src/jit/arm/SharedIC-arm.cpp b/js/src/jit/arm/SharedIC-arm.cpp index f3f907250f..c6353d6a32 100644 --- a/js/src/jit/arm/SharedIC-arm.cpp +++ b/js/src/jit/arm/SharedIC-arm.cpp @@ -201,6 +201,17 @@ ICUnaryArith_Int32::Compiler::generateStubCode(MacroAssembler& masm) // Compile -x as 0 - x. masm.as_rsb(R0.payloadReg(), R0.payloadReg(), Imm8(0)); break; + case JSOP_INC: { + RegisterOrInt32Constant rval = RegisterOrInt32Constant(R0.payloadReg()); + masm.inc32(&rval); + break; + } + case JSOP_DEC: { + RegisterOrInt32Constant rval = RegisterOrInt32Constant(R0.payloadReg()); + masm.dec32(&rval); + break; + } + default: MOZ_CRASH("Unexpected op"); } diff --git a/media/libjpeg/moz.build b/media/libjpeg/moz.build index c0b35dd1e9..f24b4fcf0f 100644 --- a/media/libjpeg/moz.build +++ b/media/libjpeg/moz.build @@ -91,6 +91,7 @@ if CONFIG['LIBJPEG_TURBO_ASFLAGS']: 'simd/arm/jidctred-neon.c', 'simd/arm/jquanti-neon.c', ] + CFLAGS += ['-mfpu=neon'] elif CONFIG['CPU_ARCH'] == 'aarch64': SOURCES += [ 'simd/jsimd_arm64.c', diff --git a/media/libyuv/libyuv.gyp b/media/libyuv/libyuv.gyp index 7f4ac5e70d..299ef9f063 100644 --- a/media/libyuv/libyuv.gyp +++ b/media/libyuv/libyuv.gyp @@ -64,7 +64,7 @@ 'defines': [ 'LIBYUV_NEON', ], - 'cflags!': [ + 'cflags_mozilla!': [ '-mfpu=vfp', '-mfpu=vfpv3', '-mfpu=vfpv3-d16', @@ -73,14 +73,14 @@ 'conditions': [ # Disable LTO in libyuv_neon target due to gcc 4.9 compiler bug. ['clang == 0 and use_lto == 1', { - 'cflags!': [ + 'cflags_mozilla!': [ '-flto', '-ffat-lto-objects', ], }], # arm64 does not need -mfpu=neon option as neon is not optional ['target_arch != "arm64"', { - 'cflags': [ + 'cflags_mozilla': [ '-mfpu=neon', # '-marm', # arm32 not thumb ], diff --git a/media/libyuv/moz.build b/media/libyuv/moz.build index b056785920..8aa6408319 100644 --- a/media/libyuv/moz.build +++ b/media/libyuv/moz.build @@ -20,10 +20,26 @@ libyuv_non_unified_sources = [ 'source/scale_common.cc', 'source/scale_uv.cc', ] + +if CONFIG['CPU_ARCH'] == 'arm': + libyuv_non_unified_sources += [ + 'source/compare_neon.cc', + 'source/rotate_neon.cc', + 'source/row_neon.cc', + 'source/scale_neon.cc', + ] +if CONFIG['CPU_ARCH'] == 'aarch64': + libyuv_non_unified_sources += [ + 'source/compare_neon64.cc', + 'source/rotate_neon64.cc', + 'source/row_neon64.cc', + 'source/scale_neon64.cc', + ] + GYP_DIRS['.'].input = 'libyuv.gyp' GYP_DIRS['.'].variables = gyp_vars GYP_DIRS['.'].sandbox_vars['FINAL_LIBRARY'] = 'xul' -GYP_DIRS['.'].non_unified_sources += libyuv_non_unified_sources +GYP_DIRS['.'].non_unified_sources += sorted(libyuv_non_unified_sources) # We allow warnings for third-party code that can be updated from upstream. GYP_DIRS['.'].sandbox_vars['ALLOW_COMPILER_WARNINGS'] = True diff --git a/old-configure.in b/old-configure.in index 41ac68b5fb..ce29bb0034 100644 --- a/old-configure.in +++ b/old-configure.in @@ -2943,7 +2943,7 @@ if test -z "$MOZ_SYSTEM_LIBVPX"; then dnl These flags are a lie; they're just used to enable the requisite dnl opcodes; actual arch detection is done at runtime. VPX_ASFLAGS="-march=armv7-a -mfpu=neon" - VPX_AS_CONVERSION='$(PERL) $(topsrcdir)/media/libvpx/build/make/ads2gas.pl' + VPX_AS_CONVERSION='$(PERL) $(topsrcdir)/media/libvpx/libvpx/build/make/ads2gas.pl' VPX_ARM_ASM=1 dnl Building with -mfpu=neon requires either the "softfp" or the dnl "hardfp" ABI. Depending on the compiler's default target, and the From 8d423095807c625b1813ba6ad7ad75674305edac Mon Sep 17 00:00:00 2001 From: Moonchild Date: Fri, 27 Oct 2023 14:18:05 +0200 Subject: [PATCH 02/10] [DOM] For now, use C++11 static_asserts only. Single-param static_assert is C++17 or later. Follow-up to 3d92b8212595769c40da7ccfbafb3898cfaaf7bd --- dom/base/Crypto.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/base/Crypto.cpp b/dom/base/Crypto.cpp index 2c72fb6692..6301fa6fd8 100644 --- a/dom/base/Crypto.cpp +++ b/dom/base/Crypto.cpp @@ -118,7 +118,7 @@ Crypto::GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray, void Crypto::RandomUUID(nsAString& aRetVal) { // NSID_LENGTH == 39 == 36 UUID chars + 2 curly braces + 1 NUL byte - static_assert(NSID_LENGTH == 39); + static_assert(NSID_LENGTH == 39, "Crypto:RandomUUID: NSID_LENGTH must be 39"); nsCOMPtr uuidgen = do_GetService("@mozilla.org/uuid-generator;1"); From 8d30faf95fc668a37b2a6362aef8eefc84d77536 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 30 Oct 2023 12:23:36 +0100 Subject: [PATCH 03/10] Issue #2364 - Re-factor AlphaBoxBlur Only blur one quadrant of a box-shadow and mirror it to the other quadrants. This applies only if the corners are symmetrical (square corners or equal corner radii) otherwise we'll fall back to the old method. --- gfx/2d/Blur.cpp | 33 +- gfx/2d/Blur.h | 8 + gfx/2d/PathHelpers.h | 9 + gfx/thebes/gfxBlur.cpp | 775 ++++++++++++++++++++------------- gfx/thebes/gfxBlur.h | 66 ++- layout/base/nsCSSRendering.cpp | 8 +- 6 files changed, 552 insertions(+), 347 deletions(-) diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp index 6a38b66669..143a39e9ad 100644 --- a/gfx/2d/Blur.cpp +++ b/gfx/2d/Blur.cpp @@ -335,10 +335,26 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, const IntSize& aBlurRadius, const Rect* aDirtyRect, const Rect* aSkipRect) - : mSpreadRadius(aSpreadRadius), - mBlurRadius(aBlurRadius), - mSurfaceAllocationSize(0) + : mSurfaceAllocationSize(0) { + Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect); +} + +AlphaBoxBlur::AlphaBoxBlur() + : mSurfaceAllocationSize(0) +{ +} + +void +AlphaBoxBlur::Init(const Rect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const Rect* aDirtyRect, + const Rect* aSkipRect) +{ + mSpreadRadius = aSpreadRadius; + mBlurRadius = aBlurRadius; + Rect rect(aRect); rect.Inflate(Size(aBlurRadius + aSpreadRadius)); rect.RoundOut(); @@ -355,8 +371,7 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, mHasDirtyRect = false; } - mRect = IntRect(int32_t(rect.x), int32_t(rect.y), - int32_t(rect.width), int32_t(rect.height)); + mRect = TruncatedToInt(rect); if (mRect.IsEmpty()) { return; } @@ -366,11 +381,8 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, // blurring/spreading we need to do. We convert it to IntRect to avoid // expensive int<->float conversions if we were to use Rect instead. Rect skipRect = *aSkipRect; - skipRect.RoundIn(); skipRect.Deflate(Size(aBlurRadius + aSpreadRadius)); - mSkipRect = IntRect(int32_t(skipRect.x), int32_t(skipRect.y), - int32_t(skipRect.width), int32_t(skipRect.height)); - + mSkipRect = RoundedIn(skipRect); mSkipRect = mSkipRect.Intersect(mRect); if (mSkipRect.IsEqualInterior(mRect)) return; @@ -397,8 +409,7 @@ AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, int32_t aStride, float aSigmaX, float aSigmaY) - : mRect(int32_t(aRect.x), int32_t(aRect.y), - int32_t(aRect.width), int32_t(aRect.height)), + : mRect(TruncatedToInt(aRect)), mSpreadRadius(), mBlurRadius(CalculateBlurRadius(Point(aSigmaX, aSigmaY))), mStride(aStride), diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h index 6fffeb2390..d4a9af3e36 100644 --- a/gfx/2d/Blur.h +++ b/gfx/2d/Blur.h @@ -68,6 +68,14 @@ public: float aSigmaX, float aSigmaY); + AlphaBoxBlur(); + + void Init(const Rect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const Rect* aDirtyRect, + const Rect* aSkipRect); + ~AlphaBoxBlur(); /** diff --git a/gfx/2d/PathHelpers.h b/gfx/2d/PathHelpers.h index 553a886b93..6b7135db1b 100644 --- a/gfx/2d/PathHelpers.h +++ b/gfx/2d/PathHelpers.h @@ -224,6 +224,15 @@ struct RectCornerRadii { return true; } + bool AreRadiiSame() const { + for (size_t i = 1; i < RectCorner::Count; i++) { + if (radii[i] != radii[0]) { + return false; + } + } + return true; + } + void Scale(Float aXScale, Float aYScale) { for (int i = 0; i < RectCorner::Count; i++) { radii[i].Scale(aXScale, aYScale); diff --git a/gfx/thebes/gfxBlur.cpp b/gfx/thebes/gfxBlur.cpp index d5878e1619..479a1b36d0 100644 --- a/gfx/thebes/gfxBlur.cpp +++ b/gfx/thebes/gfxBlur.cpp @@ -11,8 +11,7 @@ #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Blur.h" #include "mozilla/gfx/PathHelpers.h" -#include "mozilla/UniquePtr.h" -#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Maybe.h" #include "nsExpirationTracker.h" #include "nsClassHashtable.h" #include "gfxUtils.h" @@ -21,71 +20,69 @@ using namespace mozilla; using namespace mozilla::gfx; gfxAlphaBoxBlur::gfxAlphaBoxBlur() + : mData(nullptr) { } gfxAlphaBoxBlur::~gfxAlphaBoxBlur() { - mContext = nullptr; + if (mData) { + free(mData); + } } -gfxContext* +already_AddRefed gfxAlphaBoxBlur::Init(const gfxRect& aRect, const IntSize& aSpreadRadius, const IntSize& aBlurRadius, const gfxRect* aDirtyRect, const gfxRect* aSkipRect) { - mozilla::gfx::Rect rect(Float(aRect.x), Float(aRect.y), - Float(aRect.width), Float(aRect.height)); - IntSize spreadRadius(aSpreadRadius.width, aSpreadRadius.height); - IntSize blurRadius(aBlurRadius.width, aBlurRadius.height); - UniquePtr dirtyRect; - if (aDirtyRect) { - dirtyRect = MakeUnique(Float(aDirtyRect->x), - Float(aDirtyRect->y), - Float(aDirtyRect->width), - Float(aDirtyRect->height)); - } - UniquePtr skipRect; - if (aSkipRect) { - skipRect = MakeUnique(Float(aSkipRect->x), - Float(aSkipRect->y), - Float(aSkipRect->width), - Float(aSkipRect->height)); - } + Maybe dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing(); + Maybe skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing(); + RefPtr dt = + InitDrawTarget(ToRect(aRect), aSpreadRadius, aBlurRadius, + dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr)); + if (!dt) { + return nullptr; + } - mBlur = MakeUnique(rect, spreadRadius, blurRadius, dirtyRect.get(), skipRect.get()); - size_t blurDataSize = mBlur->GetSurfaceAllocationSize(); - if (blurDataSize == 0) - return nullptr; + RefPtr context = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(context); // already checked for target above + context->SetMatrix(gfxMatrix::Translation(-mBlur.GetRect().TopLeft())); + return context.forget(); +} - IntSize size = mBlur->GetSize(); +already_AddRefed +gfxAlphaBoxBlur::InitDrawTarget(const Rect& aRect, + const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, + const Rect* aDirtyRect, + const Rect* aSkipRect) +{ + mBlur.Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect); + size_t blurDataSize = mBlur.GetSurfaceAllocationSize(); + if (blurDataSize == 0) { + return nullptr; + } - // Make an alpha-only surface to draw on. We will play with the data after - // everything is drawn to create a blur effect. - mData = MakeUniqueFallible(blurDataSize); - if (!mData) { - return nullptr; - } - memset(mData.get(), 0, blurDataSize); + // Make an alpha-only surface to draw on. We will play with the data after + // everything is drawn to create a blur effect. + mData = static_cast(calloc(1, blurDataSize)); + if (!mData) { + return nullptr; + } - RefPtr dt = - gfxPlatform::CreateDrawTargetForData(mData.get(), size, - mBlur->GetStride(), - SurfaceFormat::A8); - if (!dt || !dt->IsValid()) { - return nullptr; - } - - IntRect irect = mBlur->GetRect(); - gfxPoint topleft(irect.TopLeft().x, irect.TopLeft().y); - - mContext = gfxContext::CreateOrNull(dt); - MOZ_ASSERT(mContext); // already checked for target above - mContext->SetMatrix(gfxMatrix::Translation(-topleft)); - - return mContext; + RefPtr dt = + gfxPlatform::CreateDrawTargetForData(mData, + mBlur.GetSize(), + mBlur.GetStride(), + SurfaceFormat::A8); + if (!dt || !dt->IsValid()) { + return nullptr; + } + dt->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft())); + return dt.forget(); } void @@ -121,20 +118,20 @@ DrawBlur(gfxContext* aDestinationCtx, already_AddRefed gfxAlphaBoxBlur::DoBlur(DrawTarget* aDT, IntPoint* aTopLeft) { - mBlur->Blur(mData.get()); + mBlur.Blur(mData); - *aTopLeft = mBlur->GetRect().TopLeft(); + *aTopLeft = mBlur.GetRect().TopLeft(); - return aDT->CreateSourceSurfaceFromData(mData.get(), - mBlur->GetSize(), - mBlur->GetStride(), + return aDT->CreateSourceSurfaceFromData(mData, + mBlur.GetSize(), + mBlur.GetStride(), SurfaceFormat::A8); } void gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx) { - if (!mContext) + if (!mData) return; DrawTarget *dest = aDestinationCtx->GetDrawTarget(); @@ -143,7 +140,7 @@ gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx) return; } - Rect* dirtyRect = mBlur->GetDirtyRect(); + Rect* dirtyRect = mBlur.GetDirtyRect(); IntPoint topLeft; RefPtr mask = DoBlur(dest, &topLeft); @@ -175,16 +172,15 @@ struct BlurCacheKey : public PLDHashEntryHdr { bool mIsInset; // Only used for inset blurs - bool mHasBorderRadius; IntSize mInnerMinSize; - BlurCacheKey(IntSize aMinSize, IntSize aBlurRadius, - RectCornerRadii* aCornerRadii, const Color& aShadowColor, + BlurCacheKey(const IntSize& aMinSize, const IntSize& aBlurRadius, + const RectCornerRadii* aCornerRadii, const Color& aShadowColor, BackendType aBackendType) : BlurCacheKey(aMinSize, IntSize(0, 0), aBlurRadius, aCornerRadii, aShadowColor, false, - false, aBackendType) + aBackendType) {} explicit BlurCacheKey(const BlurCacheKey* aOther) @@ -194,22 +190,20 @@ struct BlurCacheKey : public PLDHashEntryHdr { , mBackend(aOther->mBackend) , mCornerRadii(aOther->mCornerRadii) , mIsInset(aOther->mIsInset) - , mHasBorderRadius(aOther->mHasBorderRadius) , mInnerMinSize(aOther->mInnerMinSize) { } - explicit BlurCacheKey(IntSize aOuterMinSize, IntSize aInnerMinSize, - IntSize aBlurRadius, + explicit BlurCacheKey(const IntSize& aOuterMinSize, const IntSize& aInnerMinSize, + const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, const Color& aShadowColor, bool aIsInset, - bool aHasBorderRadius, BackendType aBackendType) + BackendType aBackendType) : mMinSize(aOuterMinSize) , mBlurRadius(aBlurRadius) , mShadowColor(aShadowColor) , mBackend(aBackendType) , mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii()) , mIsInset(aIsInset) - , mHasBorderRadius(aHasBorderRadius) , mInnerMinSize(aInnerMinSize) { } @@ -237,7 +231,6 @@ struct BlurCacheKey : public PLDHashEntryHdr { if (aKey->mIsInset) { hash = AddToHash(hash, aKey->mInnerMinSize.width, aKey->mInnerMinSize.height); - hash = AddToHash(hash, HashBytes(&aKey->mHasBorderRadius, sizeof(bool))); } return hash; } @@ -252,8 +245,7 @@ struct BlurCacheKey : public PLDHashEntryHdr { aKey->mBackend == mBackend) { if (mIsInset) { - return (mHasBorderRadius == aKey->mHasBorderRadius) && - (mInnerMinSize == aKey->mInnerMinSize); + return (mInnerMinSize == aKey->mInnerMinSize); } return true; @@ -274,15 +266,15 @@ struct BlurCacheKey : public PLDHashEntryHdr { * to the cache entry to be able to be tracked by the nsExpirationTracker. * */ struct BlurCacheData { - BlurCacheData(SourceSurface* aBlur, IntMargin aExtendDestBy, const BlurCacheKey& aKey) + BlurCacheData(SourceSurface* aBlur, const IntMargin& aBlurMargin, const BlurCacheKey& aKey) : mBlur(aBlur) - , mExtendDest(aExtendDestBy) + , mBlurMargin(aBlurMargin) , mKey(aKey) {} BlurCacheData(const BlurCacheData& aOther) : mBlur(aOther.mBlur) - , mExtendDest(aOther.mExtendDest) + , mBlurMargin(aOther.mBlurMargin) , mKey(aOther.mKey) { } @@ -292,7 +284,7 @@ struct BlurCacheData { nsExpirationState mExpirationState; RefPtr mBlur; - IntMargin mExtendDest; + IntMargin mBlurMargin; BlurCacheKey mKey; }; @@ -316,9 +308,9 @@ class BlurCache final : public nsExpirationTracker mHashEntries.Remove(aObject->mKey); } - BlurCacheData* Lookup(const IntSize aMinSize, + BlurCacheData* Lookup(const IntSize& aMinSize, const IntSize& aBlurRadius, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const Color& aShadowColor, BackendType aBackendType) { @@ -333,19 +325,18 @@ class BlurCache final : public nsExpirationTracker return blur; } - BlurCacheData* LookupInsetBoxShadow(const IntSize aOuterMinSize, - const IntSize aInnerMinSize, + BlurCacheData* LookupInsetBoxShadow(const IntSize& aOuterMinSize, + const IntSize& aInnerMinSize, const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, const Color& aShadowColor, - const bool& aHasBorderRadius, BackendType aBackendType) { bool insetBoxShadow = true; BlurCacheKey key(aOuterMinSize, aInnerMinSize, aBlurRadius, aCornerRadii, aShadowColor, insetBoxShadow, - aHasBorderRadius, aBackendType); + aBackendType); BlurCacheData* blur = mHashEntries.Get(key); if (blur) { MarkUsed(blur); @@ -383,28 +374,26 @@ class BlurCache final : public nsExpirationTracker static BlurCache* gBlurCache = nullptr; static IntSize -ComputeMinSizeForShadowShape(RectCornerRadii* aCornerRadii, - IntSize aBlurRadius, - IntMargin& aSlice, +ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii, + const IntSize& aBlurRadius, + IntMargin& aOutSlice, const IntSize& aRectSize) { - float cornerWidth = 0; - float cornerHeight = 0; + Size cornerSize(0, 0); if (aCornerRadii) { - RectCornerRadii corners = *aCornerRadii; - for (size_t i = 0; i < 4; i++) { - cornerWidth = std::max(cornerWidth, corners[i].width); - cornerHeight = std::max(cornerHeight, corners[i].height); + const RectCornerRadii& corners = *aCornerRadii; + for (size_t i = 0; i < RectCorner::Count; i++) { + cornerSize.width = std::max(cornerSize.width, corners[i].width); + cornerSize.height = std::max(cornerSize.height, corners[i].height); } } - aSlice = IntMargin(ceil(cornerHeight) + aBlurRadius.height, - ceil(cornerWidth) + aBlurRadius.width, - ceil(cornerHeight) + aBlurRadius.height, - ceil(cornerWidth) + aBlurRadius.width); + IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; + aOutSlice = IntMargin(margin.height, margin.width, + margin.height, margin.width); - IntSize minSize(aSlice.LeftRight() + 1, - aSlice.TopBottom() + 1); + IntSize minSize(aOutSlice.LeftRight() + 1, + aOutSlice.TopBottom() + 1); // If aRectSize is smaller than minSize, the border-image approach won't // work; there's no way to squeeze parts of the min box-shadow source @@ -414,31 +403,31 @@ ComputeMinSizeForShadowShape(RectCornerRadii* aCornerRadii, // to slice away more than we have. if (aRectSize.width < minSize.width) { minSize.width = aRectSize.width; - aSlice.left = 0; - aSlice.right = 0; + aOutSlice.left = 0; + aOutSlice.right = 0; } if (aRectSize.height < minSize.height) { minSize.height = aRectSize.height; - aSlice.top = 0; - aSlice.bottom = 0; + aOutSlice.top = 0; + aOutSlice.bottom = 0; } - MOZ_ASSERT(aSlice.LeftRight() <= minSize.width); - MOZ_ASSERT(aSlice.TopBottom() <= minSize.height); + MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width); + MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height); return minSize; } void -CacheBlur(DrawTarget& aDT, +CacheBlur(DrawTarget* aDT, const IntSize& aMinSize, const IntSize& aBlurRadius, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const Color& aShadowColor, - IntMargin aExtendDest, + const IntMargin& aBlurMargin, SourceSurface* aBoxShadow) { - BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aDT.GetBackendType()); - BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendDest, key); + BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aDT->GetBackendType()); + BlurCacheData* data = new BlurCacheData(aBoxShadow, aBlurMargin, key); if (!gBlurCache->RegisterEntry(data)) { delete data; } @@ -446,56 +435,59 @@ CacheBlur(DrawTarget& aDT, // Blurs a small surface and creates the mask. static already_AddRefed -CreateBlurMask(const IntSize& aMinSize, - RectCornerRadii* aCornerRadii, - IntSize aBlurRadius, - IntMargin& aExtendDestBy, - IntMargin& aSliceBorder, - DrawTarget& aDestDrawTarget) +CreateBlurMask(DrawTarget* aDestDrawTarget, + const IntSize& aMinSize, + const RectCornerRadii* aCornerRadii, + const IntSize& aBlurRadius, + bool aMirrorCorners, + IntMargin& aOutBlurMargin) { gfxAlphaBoxBlur blur; - IntRect minRect(IntPoint(), aMinSize); - - gfxContext* blurCtx = blur.Init(ThebesRect(Rect(minRect)), IntSize(), - aBlurRadius, nullptr, nullptr); - - if (!blurCtx) { + Rect minRect(Point(0, 0), Size(aMinSize)); + Rect blurRect(minRect); + // If mirroring corners, we only need to draw the top-left quadrant. + // Use ceil to preserve the remaining 1x1 middle area for minimized box + // shadows. + if (aMirrorCorners) { + blurRect.SizeTo(ceil(blurRect.width * 0.5f), ceil(blurRect.height * 0.5f)); + } + IntSize zeroSpread(0, 0); + RefPtr blurDT = + blur.InitDrawTarget(blurRect, zeroSpread, aBlurRadius); + if (!blurDT) { return nullptr; } - DrawTarget* blurDT = blurCtx->GetDrawTarget(); ColorPattern black(Color(0.f, 0.f, 0.f, 1.f)); if (aCornerRadii) { RefPtr roundedRect = - MakePathForRoundedRect(*blurDT, Rect(minRect), *aCornerRadii); + MakePathForRoundedRect(*blurDT, minRect, *aCornerRadii); blurDT->Fill(roundedRect, black); } else { - blurDT->FillRect(Rect(minRect), black); + blurDT->FillRect(minRect, black); } IntPoint topLeft; - RefPtr result = blur.DoBlur(&aDestDrawTarget, &topLeft); + RefPtr result = blur.DoBlur(aDestDrawTarget, &topLeft); if (!result) { return nullptr; } - IntRect expandedMinRect(topLeft, result->GetSize()); - aExtendDestBy = expandedMinRect - minRect; - aSliceBorder += aExtendDestBy; - - MOZ_ASSERT(aSliceBorder.LeftRight() <= expandedMinRect.width); - MOZ_ASSERT(aSliceBorder.TopBottom() <= expandedMinRect.height); + // Since blurRect is at (0, 0), we can find the inflated margin by + // negating the new rect origin, which would have been negative if + // the rect was inflated. + aOutBlurMargin = IntMargin(-topLeft.y, -topLeft.x, -topLeft.y, -topLeft.x); return result.forget(); } static already_AddRefed -CreateBoxShadow(DrawTarget& aDestDT, SourceSurface* aBlurMask, const Color& aShadowColor) +CreateBoxShadow(DrawTarget* aDestDT, SourceSurface* aBlurMask, const Color& aShadowColor) { IntSize blurredSize = aBlurMask->GetSize(); RefPtr boxShadowDT = - Factory::CreateDrawTarget(aDestDT.GetBackendType(), blurredSize, SurfaceFormat::B8G8R8A8); + Factory::CreateDrawTarget(aDestDT->GetBackendType(), blurredSize, SurfaceFormat::B8G8R8A8); if (!boxShadowDT) { return nullptr; @@ -510,17 +502,19 @@ static already_AddRefed GetBlur(gfxContext* aDestinationCtx, const IntSize& aRectSize, const IntSize& aBlurRadius, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const Color& aShadowColor, - IntMargin& aExtendDestBy, - IntMargin& aSlice) + bool aMirrorCorners, + IntMargin& aOutBlurMargin, + IntMargin& aOutSlice, + IntSize& aOutMinSize) { if (!gBlurCache) { gBlurCache = new BlurCache(); } IntSize minSize = - ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aSlice, aRectSize); + ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aOutSlice, aRectSize); // We can get seams using the min size rect when drawing to the destination rect // if we have a non-pixel aligned destination transformation. In those cases, @@ -530,24 +524,25 @@ GetBlur(gfxContext* aDestinationCtx, if (useDestRect) { minSize = aRectSize; } + aOutMinSize = minSize; - DrawTarget& destDT = *aDestinationCtx->GetDrawTarget(); + DrawTarget* destDT = aDestinationCtx->GetDrawTarget(); - BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius, - aCornerRadii, aShadowColor, - destDT.GetBackendType()); - if (cached && !useDestRect) { - // See CreateBlurMask() for these values - aExtendDestBy = cached->mExtendDest; - aSlice = aSlice + aExtendDestBy; - RefPtr blur = cached->mBlur; - return blur.forget(); + if (!useDestRect) { + BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius, + aCornerRadii, aShadowColor, + destDT->GetBackendType()); + if (cached) { + // See CreateBlurMask() for these values + aOutBlurMargin = cached->mBlurMargin; + RefPtr blur = cached->mBlur; + return blur.forget(); + } } RefPtr blurMask = - CreateBlurMask(minSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice, - destDT); - + CreateBlurMask(destDT, minSize, aCornerRadii, aBlurRadius, + aMirrorCorners, aOutBlurMargin); if (!blurMask) { return nullptr; } @@ -557,12 +552,9 @@ GetBlur(gfxContext* aDestinationCtx, return nullptr; } - if (useDestRect) { - // Since we're just going to paint the actual rect to the destination - aSlice.SizeTo(0, 0, 0, 0); - } else { + if (!useDestRect) { CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, - aExtendDestBy, boxShadow); + aOutBlurMargin, boxShadow); } return boxShadow.forget(); } @@ -580,49 +572,56 @@ RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom, Float aLeft) return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop); } +static bool +ShouldStretchSurface(DrawTarget* aDT, SourceSurface* aSurface) +{ + // Use stretching if possible, since it leads to less seams when the + // destination is transformed. However, don't do this if we're using cairo, + // because if cairo is using pixman it won't render anything for large + // stretch factors because pixman's internal fixed point precision is not + // high enough to handle those scale factors. + // Calling FillRect on a D2D backend with a repeating pattern is much slower + // than DrawSurface, so special case the D2D backend here. + return (!aDT->GetTransform().IsRectilinear() && + aDT->GetBackendType() != BackendType::CAIRO) || + (aDT->GetBackendType() == BackendType::DIRECT2D1_1); +} + static void -RepeatOrStretchSurface(DrawTarget& aDT, SourceSurface* aSurface, - const Rect& aDest, const Rect& aSrc, Rect& aSkipRect) +RepeatOrStretchSurface(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, const Rect& aSkipRect) { if (aSkipRect.Contains(aDest)) { return; } - if ((!aDT.GetTransform().IsRectilinear() && - aDT.GetBackendType() != BackendType::CAIRO) || - (aDT.GetBackendType() == BackendType::DIRECT2D1_1)) { - // Use stretching if possible, since it leads to less seams when the - // destination is transformed. However, don't do this if we're using cairo, - // because if cairo is using pixman it won't render anything for large - // stretch factors because pixman's internal fixed point precision is not - // high enough to handle those scale factors. - // Calling FillRect on a D2D backend with a repeating pattern is much slower - // than DrawSurface, so special case the D2D backend here. - aDT.DrawSurface(aSurface, aDest, aSrc); + if (ShouldStretchSurface(aDT, aSurface)) { + aDT->DrawSurface(aSurface, aDest, aSrc); return; } SurfacePattern pattern(aSurface, ExtendMode::REPEAT, Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()), SamplingFilter::GOOD, RoundedToInt(aSrc)); - aDT.FillRect(aDest, pattern); + aDT->FillRect(aDest, pattern); } static void -DrawCorner(DrawTarget& aDT, SourceSurface* aSurface, - const Rect& aDest, const Rect& aSrc, Rect& aSkipRect) +DrawCorner(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, const Rect& aSkipRect) { if (aSkipRect.Contains(aDest)) { return; } - aDT.DrawSurface(aSurface, aDest, aSrc); + aDT->DrawSurface(aSurface, aDest, aSrc); } static void -DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur, - Rect aDstOuter, Rect aDstInner, Rect aSrcOuter, Rect aSrcInner, - Rect aSkipRect) +DrawMinBoxShadow(DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur, + const Rect& aDstOuter, const Rect& aDstInner, + const Rect& aSrcOuter, const Rect& aSrcInner, + const Rect& aSkipRect, bool aMiddle = false) { // Corners: top left, top right, bottom left, bottom right DrawCorner(aDestDrawTarget, aSourceBlur, @@ -679,6 +678,192 @@ DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur, RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(), aSrcOuter.YMost(), aSrcInner.X()), aSkipRect); + + // Middle part + if (aMiddle) { + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), + aDstInner.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), + aSrcInner.YMost(), aSrcInner.X()), + aSkipRect); + } +} + +static void +DrawMirroredRect(DrawTarget* aDT, + SourceSurface* aSurface, + const Rect& aDest, const Point& aSrc, + Float aScaleX, Float aScaleY) +{ + SurfacePattern pattern(aSurface, ExtendMode::CLAMP, + Matrix::Scaling(aScaleX, aScaleY) + .PreTranslate(-aSrc) + .PostTranslate( + aScaleX < 0 ? aDest.XMost() : aDest.x, + aScaleY < 0 ? aDest.YMost() : aDest.y)); + aDT->FillRect(aDest, pattern); +} + +static void +DrawMirroredBoxShadow(DrawTarget* aDT, + SourceSurface* aSurface, + const Rect& aDestRect) +{ + Point center(ceil(aDestRect.x + aDestRect.width / 2), + ceil(aDestRect.y + aDestRect.height / 2)); + Rect topLeft(aDestRect.x, aDestRect.y, + center.x - aDestRect.x, + center.y - aDestRect.y); + Rect bottomRight(topLeft.BottomRight(), aDestRect.Size() - topLeft.Size()); + Rect topRight(bottomRight.x, topLeft.y, bottomRight.width, topLeft.height); + Rect bottomLeft(topLeft.x, bottomRight.y, topLeft.width, bottomRight.height); + DrawMirroredRect(aDT, aSurface, topLeft, Point(), 1, 1); + DrawMirroredRect(aDT, aSurface, topRight, Point(), -1, 1); + DrawMirroredRect(aDT, aSurface, bottomLeft, Point(), 1, -1); + DrawMirroredRect(aDT, aSurface, bottomRight, Point(), -1, -1); +} + +static void +DrawMirroredCorner(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Point& aSrc, + const Rect& aSkipRect, Float aScaleX, Float aScaleY) +{ + if (aSkipRect.Contains(aDest)) { + return; + } + + DrawMirroredRect(aDT, aSurface, aDest, aSrc, aScaleX, aScaleY); +} + +static void +RepeatOrStretchMirroredSurface(DrawTarget* aDT, SourceSurface* aSurface, + const Rect& aDest, const Rect& aSrc, + const Rect& aSkipRect, Float aScaleX, Float aScaleY) +{ + if (aSkipRect.Contains(aDest)) { + return; + } + + if (ShouldStretchSurface(aDT, aSurface)) { + aScaleX *= aDest.width / aSrc.width; + aScaleY *= aDest.height / aSrc.height; + DrawMirroredRect(aDT, aSurface, aDest, aSrc.TopLeft(), aScaleX, aScaleY); + return; + } + + SurfacePattern pattern(aSurface, ExtendMode::REPEAT, + Matrix::Scaling(aScaleX, aScaleY) + .PreTranslate(-aSrc.TopLeft()) + .PostTranslate( + aScaleX < 0 ? aDest.XMost() : aDest.x, + aScaleY < 0 ? aDest.YMost() : aDest.y), + SamplingFilter::GOOD, RoundedToInt(aSrc)); + aDT->FillRect(aDest, pattern); +} + +static void +DrawMirroredMinBoxShadow(DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur, + const Rect& aDstOuter, const Rect& aDstInner, + const Rect& aSrcOuter, const Rect& aSrcInner, + const Rect& aSkipRect, bool aMiddle = false) +{ + // Corners: top left, top right, bottom left, bottom right + // Compute quadrant bounds and then clip them to corners along + // dimensions where we need to stretch from min size. + Point center(ceil(aDstOuter.x + aDstOuter.width / 2), + ceil(aDstOuter.y + aDstOuter.height / 2)); + Rect topLeft(aDstOuter.x, aDstOuter.y, + center.x - aDstOuter.x, + center.y - aDstOuter.y); + Rect bottomRight(topLeft.BottomRight(), aDstOuter.Size() - topLeft.Size()); + Rect topRight(bottomRight.x, topLeft.y, bottomRight.width, topLeft.height); + Rect bottomLeft(topLeft.x, bottomRight.y, topLeft.width, bottomRight.height); + + // Check if the middle part has been minimized along each dimension. + // If so, those will be strecthed/drawn separately and need to be clipped out. + if (aSrcInner.width == 1) { + topLeft.SetRightEdge(aDstInner.x); + topRight.SetLeftEdge(aDstInner.XMost()); + bottomLeft.SetRightEdge(aDstInner.x); + bottomRight.SetLeftEdge(aDstInner.XMost()); + } + if (aSrcInner.height == 1) { + topLeft.SetBottomEdge(aDstInner.y); + topRight.SetBottomEdge(aDstInner.y); + bottomLeft.SetTopEdge(aDstInner.YMost()); + bottomRight.SetTopEdge(aDstInner.YMost()); + } + + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topLeft, + aSrcOuter.TopLeft(), aSkipRect, 1, 1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topRight, + aSrcOuter.TopLeft(), aSkipRect, -1, 1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomLeft, + aSrcOuter.TopLeft(), aSkipRect, 1, -1); + DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomRight, + aSrcOuter.TopLeft(), aSkipRect, -1, -1); + + // Edges: top, bottom, left, right + // Draw middle edges where they need to be stretched. The top and left + // sections that are part of the top-left quadrant will be mirrored to + // the bottom and right sections, respectively. + if (aSrcInner.width == 1) { + Rect dstTop = RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(), + aDstInner.Y(), aDstInner.X()); + Rect srcTop = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()); + Rect dstBottom = RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(), + aDstOuter.YMost(), aDstInner.X()); + Rect srcBottom = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), + aSrcInner.Y(), aSrcInner.X()); + // If we only need to stretch along the X axis and we're drawing + // the middle section, just sample all the way to the center of the + // source on the Y axis to avoid extra draw calls. + if (aMiddle && aSrcInner.height != 1) { + dstTop.SetBottomEdge(center.y); + srcTop.height = dstTop.height; + dstBottom.SetTopEdge(dstTop.YMost()); + srcBottom.height = dstBottom.height; + } + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, + dstTop, srcTop, aSkipRect, 1, 1); + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, + dstBottom, srcBottom, aSkipRect, 1, -1); + } + + if (aSrcInner.height == 1) { + Rect dstLeft = RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(), + aDstInner.YMost(), aDstOuter.X()); + Rect srcLeft = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()); + Rect dstRight = RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), + aDstInner.YMost(), aDstInner.XMost()); + Rect srcRight = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), + aSrcInner.YMost(), aSrcOuter.X()); + // Only stretching on Y axis, so sample source to the center of the X axis. + if (aMiddle && aSrcInner.width != 1) { + dstLeft.SetRightEdge(center.x); + srcLeft.width = dstLeft.width; + dstRight.SetLeftEdge(dstLeft.XMost()); + srcRight.width = dstRight.width; + } + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, + dstLeft, srcLeft, aSkipRect, 1, 1); + RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, + dstRight, srcRight, aSkipRect, -1, 1); + } + + // If we need to stretch along both dimensions, then the middle part + // must be drawn separately. + if (aMiddle && aSrcInner.width == 1 && aSrcInner.height == 1) { + RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, + RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), + aDstInner.YMost(), aDstInner.X()), + RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), + aSrcInner.YMost(), aSrcInner.X()), + aSkipRect); + } } /*** @@ -695,66 +880,69 @@ DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur, /* static */ void gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx, const gfxRect& aRect, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const gfxPoint& aBlurStdDev, const Color& aShadowColor, const gfxRect& aDirtyRect, const gfxRect& aSkipRect) { IntSize blurRadius = CalculateBlurRadius(aBlurStdDev); + bool mirrorCorners = !aCornerRadii || aCornerRadii->AreRadiiSame(); IntRect rect = RoundedToInt(ToRect(aRect)); - IntMargin extendDestBy; + IntMargin blurMargin; IntMargin slice; + IntSize minSize; RefPtr boxShadow = GetBlur(aDestinationCtx, rect.Size(), blurRadius, - aCornerRadii, aShadowColor, - extendDestBy, slice); + aCornerRadii, aShadowColor, mirrorCorners, + blurMargin, slice, minSize); if (!boxShadow) { return; } - DrawTarget& destDrawTarget = *aDestinationCtx->GetDrawTarget(); - destDrawTarget.PushClipRect(ToRect(aDirtyRect)); + DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); + destDrawTarget->PushClipRect(ToRect(aDirtyRect)); // Copy the right parts from boxShadow into destDrawTarget. The middle parts // will be stretched, border-image style. - Rect srcOuter(Point(), Size(boxShadow->GetSize())); - Rect srcInner = srcOuter; + Rect srcOuter(Point(blurMargin.left, blurMargin.top), Size(minSize)); + Rect srcInner(srcOuter); + srcOuter.Inflate(Margin(blurMargin)); srcInner.Deflate(Margin(slice)); - rect.Inflate(extendDestBy); Rect dstOuter(rect); Rect dstInner(rect); + dstOuter.Inflate(Margin(blurMargin)); dstInner.Deflate(Margin(slice)); Rect skipRect = ToRect(aSkipRect); - if (srcInner.IsEqualInterior(srcOuter)) { - MOZ_ASSERT(dstInner.IsEqualInterior(dstOuter)); + if (minSize == rect.Size()) { // The target rect is smaller than the minimal size so just draw the surface - destDrawTarget.DrawSurface(boxShadow, dstInner, srcInner); + if (mirrorCorners) { + DrawMirroredBoxShadow(destDrawTarget, boxShadow, dstOuter); + } else { + destDrawTarget->DrawSurface(boxShadow, dstOuter, srcOuter); + } } else { - DrawBoxShadows(destDrawTarget, boxShadow, dstOuter, dstInner, - srcOuter, srcInner, skipRect); - - // Middle part - RepeatOrStretchSurface(destDrawTarget, boxShadow, - RectWithEdgesTRBL(dstInner.Y(), dstInner.XMost(), - dstInner.YMost(), dstInner.X()), - RectWithEdgesTRBL(srcInner.Y(), srcInner.XMost(), - srcInner.YMost(), srcInner.X()), - skipRect); + if (mirrorCorners) { + DrawMirroredMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, + srcOuter, srcInner, skipRect, true); + } else { + DrawMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, + srcOuter, srcInner, skipRect, true); + } } - // A note about anti-aliasing and seems between adjacent parts: + // A note about anti-aliasing and seams between adjacent parts: // We don't explicitly disable anti-aliasing in the DrawSurface calls above, // so if there's a transform on destDrawTarget that is not pixel-aligned, // there will be seams between adjacent parts of the box-shadow. It's hard to // avoid those without the use of an intermediate surface. - // You might think that we could avoid those by just turning of AA, but there + // You might think that we could avoid those by just turning off AA, but there // is a problem with that: Box-shadow rendering needs to clip out the // element's border box, and we'd like that clip to have anti-aliasing - // especially if the element has rounded corners! So we can't do that unless @@ -766,13 +954,13 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx, // covered by the shape. So for pixels on the edge between two adjacent parts, // all those pixels will be painted to by both parts, which looks very bad. - destDrawTarget.PopClip(); + destDrawTarget->PopClip(); } static already_AddRefed GetBoxShadowInsetPath(DrawTarget* aDrawTarget, const Rect aOuterRect, const Rect aInnerRect, - const bool aHasBorderRadius, const RectCornerRadii& aInnerClipRadii) + const RectCornerRadii* aInnerClipRadii) { /*** * We create an inset path by having two rects. @@ -791,8 +979,8 @@ GetBoxShadowInsetPath(DrawTarget* aDrawTarget, aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD); AppendRectToPath(builder, aOuterRect, true); - if (aHasBorderRadius) { - AppendRoundedRectToPath(builder, aInnerRect, aInnerClipRadii, false); + if (aInnerClipRadii) { + AppendRoundedRectToPath(builder, aInnerRect, *aInnerClipRadii, false); } else { AppendRectToPath(builder, aInnerRect, false); } @@ -801,31 +989,28 @@ GetBoxShadowInsetPath(DrawTarget* aDrawTarget, static void FillDestinationPath(gfxContext* aDestinationCtx, - const Rect aDestinationRect, - const Rect aShadowClipRect, + const Rect& aDestinationRect, + const Rect& aShadowClipRect, const Color& aShadowColor, - const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii) + const RectCornerRadii* aInnerClipRadii = nullptr) { // When there is no blur radius, fill the path onto the destination // surface. aDestinationCtx->SetColor(aShadowColor); DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); RefPtr shadowPath = GetBoxShadowInsetPath(destDrawTarget, aDestinationRect, - aShadowClipRect, aHasBorderRadius, - aInnerClipRadii); + aShadowClipRect, aInnerClipRadii); aDestinationCtx->SetPath(shadowPath); aDestinationCtx->Fill(); } static void -CacheInsetBlur(const IntSize aMinOuterSize, - const IntSize aMinInnerSize, +CacheInsetBlur(const IntSize& aMinOuterSize, + const IntSize& aMinInnerSize, const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, const Color& aShadowColor, - const bool& aHasBorderRadius, BackendType aBackendType, SourceSurface* aBoxShadow) { @@ -833,76 +1018,76 @@ CacheInsetBlur(const IntSize aMinOuterSize, BlurCacheKey key(aMinOuterSize, aMinInnerSize, aBlurRadius, aCornerRadii, aShadowColor, isInsetBlur, - aHasBorderRadius, aBackendType); - IntMargin extendDestBy(0, 0, 0, 0); - BlurCacheData* data = new BlurCacheData(aBoxShadow, extendDestBy, key); + aBackendType); + IntMargin blurMargin(0, 0, 0, 0); + BlurCacheData* data = new BlurCacheData(aBoxShadow, blurMargin, key); if (!gBlurCache->RegisterEntry(data)) { delete data; } } -already_AddRefed -gfxAlphaBoxBlur::GetInsetBlur(const mozilla::gfx::Rect aOuterRect, - const mozilla::gfx::Rect aWhitespaceRect, - const bool aIsDestRect, - const mozilla::gfx::Color& aShadowColor, - const mozilla::gfx::IntSize& aBlurRadius, - const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - DrawTarget* aDestDrawTarget) +already_AddRefed +gfxAlphaBoxBlur::GetInsetBlur(const Rect& aOuterRect, + const Rect& aWhitespaceRect, + bool aIsDestRect, + const Color& aShadowColor, + const IntSize& aBlurRadius, + const RectCornerRadii* aInnerClipRadii, + DrawTarget* aDestDrawTarget, + bool aMirrorCorners) { if (!gBlurCache) { gBlurCache = new BlurCache(); } - IntSize outerSize((int)aOuterRect.width, (int)aOuterRect.height); - IntSize whitespaceSize((int)aWhitespaceRect.width, (int)aWhitespaceRect.height); - BlurCacheData* cached = + IntSize outerSize = IntSize::Truncate(aOuterRect.Size()); + IntSize whitespaceSize = IntSize::Truncate(aWhitespaceRect.Size()); + if (!aIsDestRect) { + BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow(outerSize, whitespaceSize, - aBlurRadius, &aInnerClipRadii, - aShadowColor, aHasBorderRadius, - aDestDrawTarget->GetBackendType()); - - if (cached && !aIsDestRect) { - // So we don't forget the actual cached blur - RefPtr cachedBlur = cached->mBlur; - return cachedBlur.forget(); + aBlurRadius, aInnerClipRadii, + aShadowColor, aDestDrawTarget->GetBackendType()); + if (cached) { + // So we don't forget the actual cached blur + RefPtr cachedBlur = cached->mBlur; + return cachedBlur.forget(); + } } // If we can do a min rect, the whitespace rect will be expanded in Init to // aOuterRect. Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect; + // If mirroring corners, we only need to draw the top-left quadrant. + // Use ceil to preserve the remaining 1x1 middle area for minimized box + // shadows. + if (aMirrorCorners) { + blurRect.SizeTo(ceil(blurRect.width * 0.5f), ceil(blurRect.height * 0.5f)); + } IntSize zeroSpread(0, 0); - gfxContext* minGfxContext = Init(ThebesRect(blurRect), - zeroSpread, aBlurRadius, - nullptr, nullptr); - if (!minGfxContext) { + RefPtr minDrawTarget = + InitDrawTarget(blurRect, zeroSpread, aBlurRadius); + if (!minDrawTarget) { return nullptr; } - // This is really annoying. When we create the AlphaBoxBlur, the gfxContext + // This is really annoying. When we create the AlphaBoxBlur, the DrawTarget // has a translation applied to it that is the topLeft point. This is actually // the rect we gave it plus the blur radius. The rects we give this for the outer // and whitespace rects are based at (0, 0). We could either translate those rects // when we don't have a destination rect or ignore the translation when using // the dest rect. The dest rects layout gives us expect this translation. if (!aIsDestRect) { - minGfxContext->SetMatrix(gfxMatrix()); + minDrawTarget->SetTransform(Matrix()); } - DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget(); - // Fill in the path between the inside white space / outer rects // NOT the inner frame RefPtr maskPath = GetBoxShadowInsetPath(minDrawTarget, aOuterRect, - aWhitespaceRect, aHasBorderRadius, - aInnerClipRadii); + aWhitespaceRect, aInnerClipRadii); - Color black(0.f, 0.f, 0.f, 1.f); - minGfxContext->SetColor(black); - minGfxContext->SetPath(maskPath); - minGfxContext->Fill(); + ColorPattern black(Color(0.f, 0.f, 0.f, 1.f)); + minDrawTarget->Fill(maskPath, black); // Create the A8 mask IntPoint topLeft; @@ -912,16 +1097,15 @@ gfxAlphaBoxBlur::GetInsetBlur(const mozilla::gfx::Rect aOuterRect, } // Fill in with the color we actually wanted - RefPtr minInsetBlur = CreateBoxShadow(*aDestDrawTarget, minMask, aShadowColor); + RefPtr minInsetBlur = CreateBoxShadow(aDestDrawTarget, minMask, aShadowColor); if (!minInsetBlur) { return nullptr; } if (!aIsDestRect) { CacheInsetBlur(outerSize, whitespaceSize, - aBlurRadius, &aInnerClipRadii, - aShadowColor, aHasBorderRadius, - aDestDrawTarget->GetBackendType(), + aBlurRadius, aInnerClipRadii, + aShadowColor, aDestDrawTarget->GetBackendType(), minInsetBlur); } @@ -951,36 +1135,35 @@ gfxAlphaBoxBlur::GetInsetBlur(const mozilla::gfx::Rect aOuterRect, * | | | * |________________________________| */ -static void GetBlurMargins(const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - const IntSize aBlurRadius, +static void GetBlurMargins(const RectCornerRadii* aInnerClipRadii, + const IntSize& aBlurRadius, Margin& aOutBlurMargin, Margin& aOutInnerMargin) { - float cornerWidth = 0; - float cornerHeight = 0; - if (aHasBorderRadius) { - for (size_t i = 0; i < 4; i++) { - cornerWidth = std::max(cornerWidth, aInnerClipRadii[i].width); - cornerHeight = std::max(cornerHeight, aInnerClipRadii[i].height); + Size cornerSize(0, 0); + if (aInnerClipRadii) { + const RectCornerRadii& corners = *aInnerClipRadii; + for (size_t i = 0; i < RectCorner::Count; i++) { + cornerSize.width = std::max(cornerSize.width, corners[i].width); + cornerSize.height = std::max(cornerSize.height, corners[i].height); } } // Only the inside whitespace size cares about the border radius size. // Outer sizes only care about blur. - int width = cornerWidth + aBlurRadius.width; - int height = cornerHeight + aBlurRadius.height; + IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; - aOutInnerMargin.SizeTo(height, width, height, width); + aOutInnerMargin.SizeTo(margin.height, margin.width, + margin.height, margin.width); aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width, aBlurRadius.height, aBlurRadius.width); } static bool -GetInsetBoxShadowRects(const Margin aBlurMargin, - const Margin aInnerMargin, - const Rect aShadowClipRect, - const Rect aDestinationRect, +GetInsetBoxShadowRects(const Margin& aBlurMargin, + const Margin& aInnerMargin, + const Rect& aShadowClipRect, + const Rect& aDestinationRect, Rect& aOutWhitespaceRect, Rect& aOutOuterRect) { @@ -1015,20 +1198,18 @@ GetInsetBoxShadowRects(const Margin aBlurMargin, void gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, - const Rect aDestinationRect, - const Rect aShadowClipRect, - const IntSize aBlurRadius, - const IntSize aSpreadRadius, + const Rect& aDestinationRect, + const Rect& aShadowClipRect, + const IntSize& aBlurRadius, const Color& aShadowColor, - bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - const Rect aSkipRect, - const Point aShadowOffset) + const RectCornerRadii* aInnerClipRadii, + const Rect& aSkipRect, + const Point& aShadowOffset) { if ((aBlurRadius.width == 0 && aBlurRadius.height == 0) || aShadowClipRect.IsEmpty()) { FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect, - aShadowColor, aHasBorderRadius, aInnerClipRadii); + aShadowColor, aInnerClipRadii); return; } @@ -1036,31 +1217,32 @@ gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, Margin innerMargin; Margin blurMargin; - GetBlurMargins(aHasBorderRadius, aInnerClipRadii, aBlurRadius, - blurMargin, innerMargin); + GetBlurMargins(aInnerClipRadii, aBlurRadius, blurMargin, innerMargin); Rect whitespaceRect; Rect outerRect; - bool useDestRect = GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect, - aDestinationRect, whitespaceRect, outerRect); + bool useDestRect = + GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect, + aDestinationRect, whitespaceRect, outerRect); - RefPtr minBlur = GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor, - aBlurRadius, aHasBorderRadius, aInnerClipRadii, - destDrawTarget); + bool mirrorCorners = !aInnerClipRadii || aInnerClipRadii->AreRadiiSame(); + RefPtr minBlur = + GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor, + aBlurRadius, aInnerClipRadii, destDrawTarget, mirrorCorners); if (!minBlur) { return; } if (useDestRect) { - IntSize blurSize = minBlur->GetSize(); - Rect srcBlur(0, 0, blurSize.width, blurSize.height); Rect destBlur = aDestinationRect; - - // The blur itself expands the rect by the blur margin, so we - // have to mimic that here. destBlur.Inflate(blurMargin); - MOZ_ASSERT(srcBlur.Size() == destBlur.Size()); - destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur); + if (mirrorCorners) { + DrawMirroredBoxShadow(destDrawTarget, minBlur.get(), destBlur); + } else { + Rect srcBlur(Point(0, 0), Size(minBlur->GetSize())); + MOZ_ASSERT(srcBlur.Size() == destBlur.Size()); + destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur); + } } else { Rect srcOuter(outerRect); Rect srcInner(srcOuter); @@ -1070,7 +1252,7 @@ gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, // The shadow clip rect already takes into account the spread radius Rect outerFillRect(aShadowClipRect); outerFillRect.Inflate(blurMargin); - FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect, aShadowColor, false, RectCornerRadii()); + FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect, aShadowColor); // Inflate once for the frame around the whitespace Rect destRect(aShadowClipRect); @@ -1080,9 +1262,16 @@ gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, Rect destInnerRect(aShadowClipRect); destInnerRect.Deflate(innerMargin); - DrawBoxShadows(*destDrawTarget, minBlur, - destRect, destInnerRect, - srcOuter, srcInner, - aSkipRect); + if (mirrorCorners) { + DrawMirroredMinBoxShadow(destDrawTarget, minBlur, + destRect, destInnerRect, + srcOuter, srcInner, + aSkipRect); + } else { + DrawMinBoxShadow(destDrawTarget, minBlur, + destRect, destInnerRect, + srcOuter, srcInner, + aSkipRect); + } } } diff --git a/gfx/thebes/gfxBlur.h b/gfx/thebes/gfxBlur.h index 9cc5f37166..ff0a051364 100644 --- a/gfx/thebes/gfxBlur.h +++ b/gfx/thebes/gfxBlur.h @@ -10,14 +10,13 @@ #include "nsSize.h" #include "gfxPoint.h" #include "mozilla/RefPtr.h" -#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/Blur.h" class gfxContext; struct gfxRect; namespace mozilla { namespace gfx { - class AlphaBoxBlur; struct Color; struct RectCornerRadii; class SourceSurface; @@ -73,21 +72,19 @@ public: * represents an area where blurring is unnecessary and shouldn't be done * for speed reasons. It is safe to pass nullptr here. */ - gfxContext* Init(const gfxRect& aRect, - const mozilla::gfx::IntSize& aSpreadRadius, - const mozilla::gfx::IntSize& aBlurRadius, - const gfxRect* aDirtyRect, - const gfxRect* aSkipRect); + already_AddRefed + Init(const gfxRect& aRect, + const mozilla::gfx::IntSize& aSpreadRadius, + const mozilla::gfx::IntSize& aBlurRadius, + const gfxRect* aDirtyRect, + const gfxRect* aSkipRect); - /** - * Returns the context that should be drawn to supply the alpha mask to be - * blurred. If the returned surface is null, then there was an error in - * its creation. - */ - gfxContext* GetContext() - { - return mContext; - } + already_AddRefed + InitDrawTarget(const mozilla::gfx::Rect& aRect, + const mozilla::gfx::IntSize& aSpreadRadius, + const mozilla::gfx::IntSize& aBlurRadius, + const mozilla::gfx::Rect* aDirtyRect = nullptr, + const mozilla::gfx::Rect* aSkipRect = nullptr); already_AddRefed DoBlur(DrawTarget* aDT, mozilla::gfx::IntPoint* aTopLeft); @@ -129,7 +126,7 @@ public: */ static void BlurRectangle(gfxContext *aDestinationCtx, const gfxRect& aRect, - RectCornerRadii* aCornerRadii, + const RectCornerRadii* aCornerRadii, const gfxPoint& aBlurStdDev, const Color& aShadowColor, const gfxRect& aDirtyRect, @@ -154,41 +151,34 @@ public: * @param aSKipRect An area in device pixels we don't have to paint in. */ void BlurInsetBox(gfxContext* aDestinationCtx, - const mozilla::gfx::Rect aDestinationRect, - const mozilla::gfx::Rect aShadowClipRect, - const mozilla::gfx::IntSize aBlurRadius, - const mozilla::gfx::IntSize aSpreadRadius, + const mozilla::gfx::Rect& aDestinationRect, + const mozilla::gfx::Rect& aShadowClipRect, + const mozilla::gfx::IntSize& aBlurRadius, const mozilla::gfx::Color& aShadowColor, - const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - const mozilla::gfx::Rect aSkipRect, - const mozilla::gfx::Point aShadowOffset); + const RectCornerRadii* aInnerClipRadii, + const mozilla::gfx::Rect& aSkipRect, + const mozilla::gfx::Point& aShadowOffset); protected: already_AddRefed - GetInsetBlur(const mozilla::gfx::Rect aOuterRect, - const mozilla::gfx::Rect aWhitespaceRect, - const bool aIsDestRect, + GetInsetBlur(const mozilla::gfx::Rect& aOuterRect, + const mozilla::gfx::Rect& aWhitespaceRect, + bool aIsDestRect, const mozilla::gfx::Color& aShadowColor, const mozilla::gfx::IntSize& aBlurRadius, - const bool aHasBorderRadius, - const RectCornerRadii& aInnerClipRadii, - DrawTarget* aDestDrawTarget); - - /** - * The context of the temporary alpha surface. - */ - RefPtr mContext; + const RectCornerRadii* aInnerClipRadii, + DrawTarget* aDestDrawTarget, + bool aMirrorCorners); /** * The temporary alpha surface. */ - mozilla::UniquePtr mData; + uint8_t* mData; /** * The object that actually does the blurring for us. */ - mozilla::UniquePtr mBlur; + mozilla::gfx::AlphaBoxBlur mBlur; }; #endif /* GFX_BLUR_H */ diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 8f42e02cb9..e8a611bf8e 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -1631,7 +1631,6 @@ nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext, // transparent in the shadow, so drawing them changes nothing. gfxContext* renderContext = aRenderingContext.ThebesContext(); DrawTarget* drawTarget = renderContext->GetDrawTarget(); - nsContextBoxBlur blurringArea; // Clip the context to the area of the frame's padding rect, so no part of the // shadow is painted outside. Also cut out anything beyond where the inset shadow @@ -6243,9 +6242,8 @@ nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx, mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect, transformedShadowClipRect, - blurRadius, spreadRadius, - aShadowColor, aHasBorderRadius, - aInnerClipRectRadii, transformedSkipRect, - aShadowOffset); + blurRadius, aShadowColor, + aHasBorderRadius ? &aInnerClipRectRadii : nullptr, + transformedSkipRect, aShadowOffset); return true; } From 4beb3b947cf686f66cd0bc73e905ed7961d5302f Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 30 Oct 2023 13:03:32 +0100 Subject: [PATCH 04/10] Issue #2364 - use DrawTarget::DrawSurfaceWithShadow to render box shadows on platforms that accelerate it. --- gfx/2d/Blur.cpp | 6 + gfx/2d/Blur.h | 11 ++ gfx/2d/DrawTargetCairo.cpp | 33 ++-- gfx/2d/DrawTargetD2D1.cpp | 20 +- gfx/2d/DrawTargetSkia.cpp | 8 +- gfx/2d/HelpersD2D.h | 6 +- gfx/thebes/gfxBlur.cpp | 243 +++++++++++++----------- gfx/thebes/gfxBlur.h | 26 ++- gfx/thebes/gfxPlatform.cpp | 11 +- layout/base/nsCSSRendering.cpp | 4 +- layout/reftests/box-shadow/reftest.list | 8 +- layout/reftests/bugs/reftest.list | 2 +- layout/reftests/outline/reftest.list | 2 +- 13 files changed, 229 insertions(+), 151 deletions(-) diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp index 143a39e9ad..d838e182f0 100644 --- a/gfx/2d/Blur.cpp +++ b/gfx/2d/Blur.cpp @@ -776,5 +776,11 @@ AlphaBoxBlur::CalculateBlurRadius(const Point& aStd) return size; } +Float +AlphaBoxBlur::CalculateBlurSigma(int32_t aBlurRadius) +{ + return aBlurRadius / GAUSSIAN_SCALE_FACTOR; +} + } // namespace gfx } // namespace mozilla diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h index d4a9af3e36..0777e1ef1c 100644 --- a/gfx/2d/Blur.h +++ b/gfx/2d/Blur.h @@ -99,6 +99,16 @@ public: */ Rect* GetDirtyRect(); + /** + * Return the spread radius, in pixels. + */ + IntSize GetSpreadRadius() const { return mSpreadRadius; } + + /** + * Return the blur radius, in pixels. + */ + IntSize GetBlurRadius() const { return mBlurRadius; } + /** * Return the minimum buffer size that should be given to Blur() method. If * zero, the class is not properly setup for blurring. Note that this @@ -122,6 +132,7 @@ public: * constructor, above. */ static IntSize CalculateBlurRadius(const Point& aStandardDeviation); + static Float CalculateBlurSigma(int32_t aBlurRadius); private: diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp index c0e4f0af26..ad22a9a456 100644 --- a/gfx/2d/DrawTargetCairo.cpp +++ b/gfx/2d/DrawTargetCairo.cpp @@ -931,16 +931,18 @@ DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface, if (cairo_surface_get_type(sourcesurf) == CAIRO_SURFACE_TYPE_TEE) { blursurf = cairo_tee_surface_index(sourcesurf, 0); surf = cairo_tee_surface_index(sourcesurf, 1); + } else { + blursurf = sourcesurf; + surf = sourcesurf; + } + if (aSigma != 0.0f) { MOZ_ASSERT(cairo_surface_get_type(blursurf) == CAIRO_SURFACE_TYPE_IMAGE); Rect extents(0, 0, width, height); AlphaBoxBlur blur(extents, cairo_image_surface_get_stride(blursurf), aSigma, aSigma); blur.Blur(cairo_image_surface_get_data(blursurf)); - } else { - blursurf = sourcesurf; - surf = sourcesurf; } WillChange(); @@ -951,25 +953,24 @@ DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface, cairo_identity_matrix(mContext); cairo_translate(mContext, aDest.x, aDest.y); - if (IsOperatorBoundByMask(aOperator)){ - cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a); - cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y); + bool needsGroup = !IsOperatorBoundByMask(aOperator); + if (needsGroup) { + cairo_push_group(mContext); + } + cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a); + cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y); + + if (blursurf != surf || + aSurface->GetFormat() != SurfaceFormat::A8) { // Now that the shadow has been drawn, we can draw the surface on top. cairo_set_source_surface(mContext, surf, 0, 0); cairo_new_path(mContext); cairo_rectangle(mContext, 0, 0, width, height); cairo_fill(mContext); - } else { - cairo_push_group(mContext); - cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a); - cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y); + } - // Now that the shadow has been drawn, we can draw the surface on top. - cairo_set_source_surface(mContext, surf, 0, 0); - cairo_new_path(mContext); - cairo_rectangle(mContext, 0, 0, width, height); - cairo_fill(mContext); + if (needsGroup) { cairo_pop_group_to_source(mContext); cairo_paint(mContext); } @@ -1924,7 +1925,7 @@ DrawTargetCairo::CreateShadowDrawTarget(const IntSize &aSize, SurfaceFormat aFor // If we don't have a blur then we can use the RGBA mask and keep all the // operations in graphics memory. - if (aSigma == 0.0F) { + if (aSigma == 0.0f || aFormat == SurfaceFormat::A8) { RefPtr target = new DrawTargetCairo(); if (target->InitAlreadyReferenced(similar, aSize)) { return target.forget(); diff --git a/gfx/2d/DrawTargetD2D1.cpp b/gfx/2d/DrawTargetD2D1.cpp index a2e8541078..1e1f49ba47 100644 --- a/gfx/2d/DrawTargetD2D1.cpp +++ b/gfx/2d/DrawTargetD2D1.cpp @@ -244,21 +244,29 @@ DrawTargetD2D1::DrawSurfaceWithShadow(SourceSurface *aSurface, // Step 1, create the shadow effect. RefPtr shadowEffect; - HRESULT hr = mDC->CreateEffect(CLSID_D2D1Shadow, getter_AddRefs(shadowEffect)); + HRESULT hr = mDC->CreateEffect(mFormat == SurfaceFormat::A8 ? CLSID_D2D1GaussianBlur : CLSID_D2D1Shadow, + getter_AddRefs(shadowEffect)); if (FAILED(hr) || !shadowEffect) { gfxWarning() << "Failed to create shadow effect. Code: " << hexa(hr); return; } shadowEffect->SetInput(0, image); - shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, aSigma); - D2D1_VECTOR_4F color = { aColor.r, aColor.g, aColor.b, aColor.a }; - shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, color); + if (mFormat == SurfaceFormat::A8) { + shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, aSigma); + shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, D2D1_BORDER_MODE_HARD); + } else { + shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, aSigma); + D2D1_VECTOR_4F color = { aColor.r, aColor.g, aColor.b, aColor.a }; + shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, color); + } D2D1_POINT_2F shadowPoint = D2DPoint(aDest + aOffset); mDC->DrawImage(shadowEffect, &shadowPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator)); - D2D1_POINT_2F imgPoint = D2DPoint(aDest); - mDC->DrawImage(image, &imgPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator)); + if (aSurface->GetFormat() != SurfaceFormat::A8) { + D2D1_POINT_2F imgPoint = D2DPoint(aDest); + mDC->DrawImage(image, &imgPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator)); + } } void diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp index 7e71e54ccf..130623658e 100644 --- a/gfx/2d/DrawTargetSkia.cpp +++ b/gfx/2d/DrawTargetSkia.cpp @@ -713,9 +713,11 @@ DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface, mCanvas->drawImage(image, shadowDest.x, shadowDest.y, &shadowPaint); } - // Composite the original image after the shadow - auto dest = IntPoint::Round(aDest); - mCanvas->drawImage(image, dest.x, dest.y, &paint); + if (aSurface->GetFormat() != SurfaceFormat::A8) { + // Composite the original image after the shadow + auto dest = IntPoint::Round(aDest); + mCanvas->drawImage(image, dest.x, dest.y, &paint); + } mCanvas->restore(); } diff --git a/gfx/2d/HelpersD2D.h b/gfx/2d/HelpersD2D.h index 48aec2cb5e..455b158671 100644 --- a/gfx/2d/HelpersD2D.h +++ b/gfx/2d/HelpersD2D.h @@ -595,6 +595,8 @@ CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestin // // + int Bpp = BytesPerPixel(aSurface->GetFormat()); + if (uploadRect.Contains(rect)) { // Extend mode is irrelevant, the displayed rect is completely contained // by the source bitmap. @@ -631,7 +633,7 @@ CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestin // A partial upload will suffice. aRT->CreateBitmap(D2D1::SizeU(uint32_t(uploadRect.width), uint32_t(uploadRect.height)), - mapping.GetData() + int(uploadRect.x) * 4 + int(uploadRect.y) * mapping.GetStride(), + mapping.GetData() + int(uploadRect.x) * Bpp + int(uploadRect.y) * mapping.GetStride(), mapping.GetStride(), D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())), getter_AddRefs(bitmap)); @@ -641,8 +643,6 @@ CreatePartialBitmapForSurface(DataSourceSurface *aSurface, const Matrix &aDestin return bitmap.forget(); } else { - int Bpp = BytesPerPixel(aSurface->GetFormat()); - if (Bpp != 4) { // This shouldn't actually happen in practice! MOZ_ASSERT(false); diff --git a/gfx/thebes/gfxBlur.cpp b/gfx/thebes/gfxBlur.cpp index 479a1b36d0..77003d3f9a 100644 --- a/gfx/thebes/gfxBlur.cpp +++ b/gfx/thebes/gfxBlur.cpp @@ -20,7 +20,8 @@ using namespace mozilla; using namespace mozilla::gfx; gfxAlphaBoxBlur::gfxAlphaBoxBlur() - : mData(nullptr) + : mData(nullptr), + mAccelerated(false) { } @@ -32,16 +33,18 @@ gfxAlphaBoxBlur::~gfxAlphaBoxBlur() } already_AddRefed -gfxAlphaBoxBlur::Init(const gfxRect& aRect, +gfxAlphaBoxBlur::Init(gfxContext* aDestinationCtx, + const gfxRect& aRect, const IntSize& aSpreadRadius, const IntSize& aBlurRadius, const gfxRect* aDirtyRect, const gfxRect* aSkipRect) { + DrawTarget* refDT = aDestinationCtx->GetDrawTarget(); Maybe dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing(); Maybe skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing(); RefPtr dt = - InitDrawTarget(ToRect(aRect), aSpreadRadius, aBlurRadius, + InitDrawTarget(refDT, ToRect(aRect), aSpreadRadius, aBlurRadius, dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr)); if (!dt) { return nullptr; @@ -54,7 +57,8 @@ gfxAlphaBoxBlur::Init(const gfxRect& aRect, } already_AddRefed -gfxAlphaBoxBlur::InitDrawTarget(const Rect& aRect, +gfxAlphaBoxBlur::InitDrawTarget(const DrawTarget* aReferenceDT, + const Rect& aRect, const IntSize& aSpreadRadius, const IntSize& aBlurRadius, const Rect* aDirtyRect, @@ -66,90 +70,134 @@ gfxAlphaBoxBlur::InitDrawTarget(const Rect& aRect, return nullptr; } - // Make an alpha-only surface to draw on. We will play with the data after - // everything is drawn to create a blur effect. - mData = static_cast(calloc(1, blurDataSize)); - if (!mData) { - return nullptr; - } + BackendType backend = aReferenceDT->GetBackendType(); - RefPtr dt = - gfxPlatform::CreateDrawTargetForData(mData, + // Check if the backend has an accelerated DrawSurfaceWithShadow. + // Currently, only D2D1.1 supports this. + // Otherwise, DrawSurfaceWithShadow only supports square blurs without spread. + if (aBlurRadius.IsSquare() && aSpreadRadius.IsEmpty() && + backend == BackendType::DIRECT2D1_1) { + mAccelerated = true; + mDrawTarget = + aReferenceDT->CreateShadowDrawTarget(mBlur.GetSize(), + SurfaceFormat::A8, + AlphaBoxBlur::CalculateBlurSigma(aBlurRadius.width)); + } else { + // Make an alpha-only surface to draw on. We will play with the data after + // everything is drawn to create a blur effect. + mData = static_cast(calloc(1, blurDataSize)); + if (!mData) { + return nullptr; + } + mDrawTarget = + Factory::DoesBackendSupportDataDrawtarget(backend) ? + Factory::CreateDrawTargetForData(backend, + mData, mBlur.GetSize(), mBlur.GetStride(), - SurfaceFormat::A8); - if (!dt || !dt->IsValid()) { + SurfaceFormat::A8) : + gfxPlatform::CreateDrawTargetForData(mData, + mBlur.GetSize(), + mBlur.GetStride(), + SurfaceFormat::A8); + } + + if (!mDrawTarget || !mDrawTarget->IsValid()) { return nullptr; } - dt->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft())); - return dt.forget(); -} - -void -DrawBlur(gfxContext* aDestinationCtx, - SourceSurface* aBlur, - const IntPoint& aTopLeft, - const Rect* aDirtyRect) -{ - DrawTarget *dest = aDestinationCtx->GetDrawTarget(); - - RefPtr thebesPat = aDestinationCtx->GetPattern(); - Pattern* pat = thebesPat->GetPattern(dest, nullptr); - - Matrix oldTransform = dest->GetTransform(); - Matrix newTransform = oldTransform; - newTransform.PreTranslate(aTopLeft.x, aTopLeft.y); - - // Avoid a semi-expensive clip operation if we can, otherwise - // clip to the dirty rect - if (aDirtyRect) { - dest->PushClipRect(*aDirtyRect); - } - - dest->SetTransform(newTransform); - dest->MaskSurface(*pat, aBlur, Point(0, 0)); - dest->SetTransform(oldTransform); - - if (aDirtyRect) { - dest->PopClip(); - } + mDrawTarget->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft())); + return do_AddRef(mDrawTarget); } already_AddRefed -gfxAlphaBoxBlur::DoBlur(DrawTarget* aDT, IntPoint* aTopLeft) +gfxAlphaBoxBlur::DoBlur(const Color* aShadowColor, IntPoint* aOutTopLeft) { + if (mData) { mBlur.Blur(mData); + } - *aTopLeft = mBlur.GetRect().TopLeft(); + if (aOutTopLeft) { + *aOutTopLeft = mBlur.GetRect().TopLeft(); + } - return aDT->CreateSourceSurfaceFromData(mData, - mBlur.GetSize(), - mBlur.GetStride(), - SurfaceFormat::A8); + RefPtr blurMask = mDrawTarget->Snapshot(); + if (mAccelerated) { + RefPtr blurDT = + Factory::CreateDrawTarget(mDrawTarget->GetBackendType(), + blurMask->GetSize(), + SurfaceFormat::A8); + if (!blurDT) { + return nullptr; + } + blurDT->DrawSurfaceWithShadow(blurMask, Point(0, 0), Color(1, 1, 1), Point(0, 0), + AlphaBoxBlur::CalculateBlurSigma(mBlur.GetBlurRadius().width), + CompositionOp::OP_OVER); + blurMask = blurDT->Snapshot(); + } + + if (!aShadowColor) { + return blurMask.forget(); + } + + RefPtr shadowDT = + Factory::CreateDrawTarget(mDrawTarget->GetBackendType(), + blurMask->GetSize(), + SurfaceFormat::B8G8R8A8); + if (!shadowDT) { + return nullptr; + } + ColorPattern shadowColor(ToDeviceColor(*aShadowColor)); + shadowDT->MaskSurface(shadowColor, blurMask, Point(0, 0)); + + return shadowDT->Snapshot(); } void gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx) { - if (!mData) - return; + if (!mAccelerated && !mData) { + return; + } - DrawTarget *dest = aDestinationCtx->GetDrawTarget(); - if (!dest) { - NS_WARNING("Blurring not supported for Thebes contexts!"); - return; - } + DrawTarget *dest = aDestinationCtx->GetDrawTarget(); + if (!dest) { + NS_WARNING("Blurring not supported for Thebes contexts!"); + return; + } - Rect* dirtyRect = mBlur.GetDirtyRect(); + RefPtr thebesPat = aDestinationCtx->GetPattern(); + Pattern* pat = thebesPat->GetPattern(dest, nullptr); + if (!pat) { + NS_WARNING("Failed to get pattern for blur!"); + return; + } - IntPoint topLeft; - RefPtr mask = DoBlur(dest, &topLeft); - if (!mask) { - NS_ERROR("Failed to create mask!"); - return; - } + IntPoint topLeft; + RefPtr mask = DoBlur(nullptr, &topLeft); + if (!mask) { + NS_ERROR("Failed to create mask!"); + return; + } - DrawBlur(aDestinationCtx, mask, topLeft, dirtyRect); + // Avoid a semi-expensive clip operation if we can, otherwise + // clip to the dirty rect + Rect* dirtyRect = mBlur.GetDirtyRect(); + if (dirtyRect) { + dest->PushClipRect(*dirtyRect); + } + + Matrix oldTransform = dest->GetTransform(); + Matrix newTransform = oldTransform; + newTransform.PreTranslate(topLeft); + dest->SetTransform(newTransform); + + dest->MaskSurface(*pat, mask, Point(0, 0)); + + dest->SetTransform(oldTransform); + + if (dirtyRect) { + dest->PopClip(); + } } IntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd) @@ -433,14 +481,15 @@ CacheBlur(DrawTarget* aDT, } } -// Blurs a small surface and creates the mask. +// Blurs a small surface and creates the colored box shadow. static already_AddRefed -CreateBlurMask(DrawTarget* aDestDrawTarget, - const IntSize& aMinSize, - const RectCornerRadii* aCornerRadii, - const IntSize& aBlurRadius, - bool aMirrorCorners, - IntMargin& aOutBlurMargin) +CreateBoxShadow(DrawTarget* aDestDrawTarget, + const IntSize& aMinSize, + const RectCornerRadii* aCornerRadii, + const IntSize& aBlurRadius, + const Color& aShadowColor, + bool aMirrorCorners, + IntMargin& aOutBlurMargin) { gfxAlphaBoxBlur blur; Rect minRect(Point(0, 0), Size(aMinSize)); @@ -453,7 +502,7 @@ CreateBlurMask(DrawTarget* aDestDrawTarget, } IntSize zeroSpread(0, 0); RefPtr blurDT = - blur.InitDrawTarget(blurRect, zeroSpread, aBlurRadius); + blur.InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius); if (!blurDT) { return nullptr; } @@ -469,7 +518,7 @@ CreateBlurMask(DrawTarget* aDestDrawTarget, } IntPoint topLeft; - RefPtr result = blur.DoBlur(aDestDrawTarget, &topLeft); + RefPtr result = blur.DoBlur(&aShadowColor, &topLeft); if (!result) { return nullptr; } @@ -482,22 +531,6 @@ CreateBlurMask(DrawTarget* aDestDrawTarget, return result.forget(); } -static already_AddRefed -CreateBoxShadow(DrawTarget* aDestDT, SourceSurface* aBlurMask, const Color& aShadowColor) -{ - IntSize blurredSize = aBlurMask->GetSize(); - RefPtr boxShadowDT = - Factory::CreateDrawTarget(aDestDT->GetBackendType(), blurredSize, SurfaceFormat::B8G8R8A8); - - if (!boxShadowDT) { - return nullptr; - } - - ColorPattern shadowColor(ToDeviceColor(aShadowColor)); - boxShadowDT->MaskSurface(shadowColor, aBlurMask, Point(0, 0)); - return boxShadowDT->Snapshot(); -} - static already_AddRefed GetBlur(gfxContext* aDestinationCtx, const IntSize& aRectSize, @@ -533,21 +566,16 @@ GetBlur(gfxContext* aDestinationCtx, aCornerRadii, aShadowColor, destDT->GetBackendType()); if (cached) { - // See CreateBlurMask() for these values + // See CreateBoxShadow() for these values aOutBlurMargin = cached->mBlurMargin; RefPtr blur = cached->mBlur; return blur.forget(); } } - RefPtr blurMask = - CreateBlurMask(destDT, minSize, aCornerRadii, aBlurRadius, - aMirrorCorners, aOutBlurMargin); - if (!blurMask) { - return nullptr; - } - - RefPtr boxShadow = CreateBoxShadow(destDT, blurMask, aShadowColor); + RefPtr boxShadow = + CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, + aShadowColor, aMirrorCorners, aOutBlurMargin); if (!boxShadow) { return nullptr; } @@ -1065,7 +1093,7 @@ gfxAlphaBoxBlur::GetInsetBlur(const Rect& aOuterRect, } IntSize zeroSpread(0, 0); RefPtr minDrawTarget = - InitDrawTarget(blurRect, zeroSpread, aBlurRadius); + InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius); if (!minDrawTarget) { return nullptr; } @@ -1089,15 +1117,8 @@ gfxAlphaBoxBlur::GetInsetBlur(const Rect& aOuterRect, ColorPattern black(Color(0.f, 0.f, 0.f, 1.f)); minDrawTarget->Fill(maskPath, black); - // Create the A8 mask - IntPoint topLeft; - RefPtr minMask = DoBlur(minDrawTarget, &topLeft); - if (!minMask) { - return nullptr; - } - - // Fill in with the color we actually wanted - RefPtr minInsetBlur = CreateBoxShadow(aDestDrawTarget, minMask, aShadowColor); + // Blur and fill in with the color we actually wanted + RefPtr minInsetBlur = DoBlur(&aShadowColor); if (!minInsetBlur) { return nullptr; } diff --git a/gfx/thebes/gfxBlur.h b/gfx/thebes/gfxBlur.h index ff0a051364..9ff5d14a34 100644 --- a/gfx/thebes/gfxBlur.h +++ b/gfx/thebes/gfxBlur.h @@ -56,6 +56,9 @@ public: /** * Constructs a box blur and initializes the temporary surface. + * + * @param aDestinationCtx The destination to blur to. + * * @param aRect The coordinates of the surface to create in device units. * * @param aBlurRadius The blur radius in pixels. This is the radius of @@ -73,21 +76,27 @@ public: * for speed reasons. It is safe to pass nullptr here. */ already_AddRefed - Init(const gfxRect& aRect, + Init(gfxContext* aDestinationCtx, + const gfxRect& aRect, const mozilla::gfx::IntSize& aSpreadRadius, const mozilla::gfx::IntSize& aBlurRadius, const gfxRect* aDirtyRect, const gfxRect* aSkipRect); already_AddRefed - InitDrawTarget(const mozilla::gfx::Rect& aRect, + InitDrawTarget(const mozilla::gfx::DrawTarget* aReferenceDT, + const mozilla::gfx::Rect& aRect, const mozilla::gfx::IntSize& aSpreadRadius, const mozilla::gfx::IntSize& aBlurRadius, const mozilla::gfx::Rect* aDirtyRect = nullptr, const mozilla::gfx::Rect* aSkipRect = nullptr); + /** + * Performs the blur and optionally colors the result if aShadowColor is not null. + */ already_AddRefed - DoBlur(DrawTarget* aDT, mozilla::gfx::IntPoint* aTopLeft); + DoBlur(const mozilla::gfx::Color* aShadowColor = nullptr, + mozilla::gfx::IntPoint* aOutTopLeft = nullptr); /** * Does the actual blurring/spreading and mask applying. Users of this @@ -170,6 +179,12 @@ protected: DrawTarget* aDestDrawTarget, bool aMirrorCorners); + + /** + * The DrawTarget of the temporary alpha surface. + */ + RefPtr mDrawTarget; + /** * The temporary alpha surface. */ @@ -179,6 +194,11 @@ protected: * The object that actually does the blurring for us. */ mozilla::gfx::AlphaBoxBlur mBlur; + + /** + * Indicates using DrawTarget-accelerated blurs. + */ + bool mAccelerated; }; #endif /* GFX_BLUR_H */ diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index 718fec151b..61e12493c3 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -1309,7 +1309,12 @@ gfxPlatform::CreateSimilarSoftwareDrawTarget(DrawTarget* aDT, if (Factory::DoesBackendSupportDataDrawtarget(aDT->GetBackendType())) { dt = aDT->CreateSimilarDrawTarget(aSize, aFormat); } else { - dt = Factory::CreateDrawTarget(BackendType::SKIA, aSize, aFormat); +#ifdef USE_SKIA + BackendType backendType = BackendType::SKIA; +#else + BackendType backendType = BackendType::CAIRO; +#endif + dt = Factory::CreateDrawTarget(backendType, aSize, aFormat); } return dt.forget(); @@ -1322,7 +1327,11 @@ gfxPlatform::CreateDrawTargetForData(unsigned char* aData, const IntSize& aSize, NS_ASSERTION(backendType != BackendType::NONE, "No backend."); if (!Factory::DoesBackendSupportDataDrawtarget(backendType)) { +#ifdef USE_SKIA + backendType = BackendType::SKIA; +#else backendType = BackendType::CAIRO; +#endif } RefPtr dt = Factory::CreateDrawTargetForData(backendType, diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index e8a611bf8e..2e2b0d0c84 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -6029,10 +6029,10 @@ nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius, dirtyRect = transform.TransformBounds(dirtyRect); if (aSkipRect) { gfxRect skipRect = transform.TransformBounds(*aSkipRect); - mContext = mAlphaBoxBlur.Init(rect, spreadRadius, + mContext = mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius, &dirtyRect, &skipRect); } else { - mContext = mAlphaBoxBlur.Init(rect, spreadRadius, + mContext = mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius, &dirtyRect, nullptr); } diff --git a/layout/reftests/box-shadow/reftest.list b/layout/reftests/box-shadow/reftest.list index 8f62299432..0c3bb1d38a 100644 --- a/layout/reftests/box-shadow/reftest.list +++ b/layout/reftests/box-shadow/reftest.list @@ -18,19 +18,19 @@ fuzzy-if(skiaContent,1,50) HTTP(..) == boxshadow-dynamic.xul boxshadow-dynamic-r random-if(d2d) == boxshadow-onecorner.html boxshadow-onecorner-ref.html random-if(d2d) == boxshadow-twocorners.html boxshadow-twocorners-ref.html random-if(d2d) == boxshadow-threecorners.html boxshadow-threecorners-ref.html -== boxshadow-skiprect.html boxshadow-skiprect-ref.html +fuzzy(2,440) == boxshadow-skiprect.html boxshadow-skiprect-ref.html == boxshadow-opacity.html boxshadow-opacity-ref.html == boxshadow-color-rounding.html boxshadow-color-rounding-ref.html == boxshadow-color-rounding-middle.html boxshadow-color-rounding-middle-ref.html -fuzzy-if(OSX==1010,1,24) fuzzy-if(d2d,16,568) == boxshadow-large-border-radius.html boxshadow-large-border-radius-ref.html # Bug 1209649 -fuzzy-if(d2d,2,1080) == boxshadow-border-radius-int.html boxshadow-border-radius-int-ref.html +fuzzy-if(OSX==1010,1,24) fuzzy-if(d2d,16,908) == boxshadow-large-border-radius.html boxshadow-large-border-radius-ref.html # Bug 1209649 +fuzzy(3,500) fuzzy-if(d2d,2,1080) == boxshadow-border-radius-int.html boxshadow-border-radius-int-ref.html == boxshadow-inset-neg-spread.html about:blank == boxshadow-inset-neg-spread2.html boxshadow-inset-neg-spread2-ref.html fuzzy(26,3610) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264 == boxshadow-inset-large-border-radius.html boxshadow-inset-large-border-radius-ref.html # fuzzy due to blur going inside, but as long as it's essentially black instead of a light gray its ok. -fuzzy(12,9445) == boxshadow-inset-large-offset.html boxshadow-inset-large-offset-ref.html +fuzzy(12,9445) fuzzy-if(d2d,13,10926) == boxshadow-inset-large-offset.html boxshadow-inset-large-offset-ref.html == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref.html == overflow-not-scrollable-1.html overflow-not-scrollable-1-ref2.html diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index e6cc895d94..3591f38c0c 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1818,7 +1818,7 @@ HTTP(..) == 983985-2.html 983985-2-ref.html == 985303-1a.html 985303-1-ref.html == 985303-1b.html 985303-1-ref.html == 987680-1.html 987680-1-ref.html -fuzzy-if(d2d,1,36) == 991046-1.html 991046-1-ref.html +fuzzy-if(d2d,1,601) == 991046-1.html 991046-1-ref.html pref(layout.css.overflow-clip-box.enabled,true) fuzzy-if(skiaContent,2,845) == 992447.html 992447-ref.html == 1003425-1.html 1003425-1-ref.html == 1003425-2.html 1003425-2-ref.html diff --git a/layout/reftests/outline/reftest.list b/layout/reftests/outline/reftest.list index a729d892e9..df7e11564a 100644 --- a/layout/reftests/outline/reftest.list +++ b/layout/reftests/outline/reftest.list @@ -1,4 +1,4 @@ -== outline-and-box-shadow.html outline-and-box-shadow-ref.html +fuzzy(2,18) == outline-and-box-shadow.html outline-and-box-shadow-ref.html == outline-and-3d-transform-1a.html outline-and-3d-transform-1-ref.html == outline-and-3d-transform-1b.html outline-and-3d-transform-1-ref.html fuzzy-if(gtkWidget,136,120) fuzzy-if(d2d,16,96) fuzzy-if(cocoaWidget,255,120) fuzzy-if(winWidget,255,216) == outline-and-3d-transform-2.html outline-and-3d-transform-2-ref.html From 3f618a55dc2a4b2d9d18013485b6787c70aa41c9 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 30 Oct 2023 13:51:08 +0100 Subject: [PATCH 05/10] Issue #2364 - Optimize the software-based box blur implementation. --- gfx/2d/Blur.cpp | 426 +++++++++++++++++++++++++++++------------------- 1 file changed, 254 insertions(+), 172 deletions(-) diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp index d838e182f0..73f09ba1af 100644 --- a/gfx/2d/Blur.cpp +++ b/gfx/2d/Blur.cpp @@ -25,159 +25,268 @@ namespace mozilla { namespace gfx { /** - * Box blur involves looking at one pixel, and setting its value to the average - * of its neighbouring pixels. - * @param aInput The input buffer. - * @param aOutput The output buffer. - * @param aLeftLobe The number of pixels to blend on the left. - * @param aRightLobe The number of pixels to blend on the right. - * @param aWidth The number of columns in the buffers. - * @param aRows The number of rows in the buffers. - * @param aSkipRect An area to skip blurring in. - * XXX shouldn't we pass stride in separately here? + * Helper function to process each row of the box blur. + * It takes care of transposing the data on input or output depending + * on whether we intend a horizontal or vertical blur, and whether we're + * reading from the initial source or writing to the final destination. + * It allows starting or ending anywhere within the row to accomodate + * a skip rect. */ -static void -BoxBlurHorizontal(unsigned char* aInput, - unsigned char* aOutput, - int32_t aLeftLobe, - int32_t aRightLobe, - int32_t aWidth, - int32_t aRows, - const IntRect& aSkipRect) +template +static inline void +BoxBlurRow(const uint8_t* aInput, + uint8_t* aOutput, + int32_t aLeftLobe, + int32_t aRightLobe, + int32_t aWidth, + int32_t aStride, + int32_t aStart, + int32_t aEnd) { - MOZ_ASSERT(aWidth > 0); + // If the input or output is transposed, then we will move down a row + // for each step, instead of moving over a column. Since these values + // only depend on a template parameter, they will more easily get + // copy-propagated in the non-transposed case, which is why they + // are not passed as parameters. + const int32_t inputStep = aTransposeInput ? aStride : 1; + const int32_t outputStep = aTransposeOutput ? aStride : 1; - int32_t boxSize = aLeftLobe + aRightLobe + 1; - bool skipRectCoversWholeRow = 0 >= aSkipRect.x && - aWidth <= aSkipRect.XMost(); - if (boxSize == 1) { - memcpy(aOutput, aInput, aWidth*aRows); - return; + // We need to sample aLeftLobe pixels to the left and aRightLobe pixels + // to the right of the current position, then average them. So this is + // the size of the total width of this filter. + const int32_t boxSize = aLeftLobe + aRightLobe + 1; + + // Instead of dividing the pixel sum by boxSize to average, we can just + // compute a scale that will normalize the result so that it can be quickly + // shifted into the desired range. + const uint32_t reciprocal = (1 << 24) / boxSize; + + // The shift would normally truncate the result, whereas we would rather + // prefer to round the result to the closest increment. By adding 0.5 units + // to the initial sum, we bias the sum so that it will be rounded by the + // truncation instead. + uint32_t alphaSum = (boxSize + 1) / 2; + + // We process the row with a moving filter, keeping a sum (alphaSum) of + // boxSize pixels. As we move over a pixel, we need to add on a pixel + // from the right extreme of the window that moved into range, and subtract + // off a pixel from the left extreme of window that moved out of range. + // But first, we need to initialization alphaSum to the contents of + // the window before we can get going. If the window moves out of bounds + // of the row, we clamp each sample to be the closest pixel from within + // row bounds, so the 0th and aWidth-1th pixel. + int32_t initLeft = aStart - aLeftLobe; + if (initLeft < 0) { + // If the left lobe samples before the row, add in clamped samples. + alphaSum += -initLeft * aInput[0]; + initLeft = 0; + } + int32_t initRight = aStart + boxSize - aLeftLobe; + if (initRight > aWidth) { + // If the right lobe samples after the row, add in clamped samples. + alphaSum += (initRight - aWidth) * aInput[(aWidth - 1) * inputStep]; + initRight = aWidth; + } + // Finally, add in all the valid, non-clamped samples to fill up the + // rest of the window. + const uint8_t* src = &aInput[initLeft * inputStep]; + const uint8_t* iterEnd = &aInput[initRight * inputStep]; + + #define INIT_ITER \ + alphaSum += *src; \ + src += inputStep; + + // We unroll the per-pixel loop here substantially. The amount of work + // done per sample is so small that the cost of a loop condition check + // and a branch can substantially add to or even dominate the performance + // of the loop. + while (src + 16 * inputStep <= iterEnd) { + INIT_ITER; INIT_ITER; INIT_ITER; INIT_ITER; + INIT_ITER; INIT_ITER; INIT_ITER; INIT_ITER; + INIT_ITER; INIT_ITER; INIT_ITER; INIT_ITER; + INIT_ITER; INIT_ITER; INIT_ITER; INIT_ITER; + } + while (src < iterEnd) { + INIT_ITER; + } + + // Now we start moving the window over the row. We will be accessing + // pixels form aStart - aLeftLobe up to aEnd + aRightLobe, which may be + // out of bounds of the row. To avoid having to check within the inner + // loops if we are in bound, we instead compute the points at which + // we will move out of bounds of the row on the left side (splitLeft) + // and right side (splitRight). + int32_t splitLeft = min(max(aLeftLobe, aStart), aEnd); + int32_t splitRight = min(max(aWidth - (boxSize - aLeftLobe), aStart), aEnd); + // If the filter window is actually large than the size of the row, + // there will be a middle area of overlap where the leftmost and rightmost + // pixel of the filter will both be outside the row. In this case, we need + // to invert the splits so that splitLeft <= splitRight. + if (boxSize > aWidth) { + swap(splitLeft, splitRight); + } + + // Process all pixels up to splitLeft that would sample before the start of the row. + // Note that because inputStep and outputStep may not be a const 1 value, it is more + // performant to increment pointers here for the source and destination rather than + // use a loop counter, since doing so would entail an expensive multiplication that + // significantly slows down the loop. + uint8_t* dst = &aOutput[aStart * outputStep]; + iterEnd = &aOutput[splitLeft * outputStep]; + src = &aInput[(aStart + boxSize - aLeftLobe) * inputStep]; + uint8_t firstVal = aInput[0]; + + #define LEFT_ITER \ + *dst = (alphaSum * reciprocal) >> 24; \ + alphaSum += *src - firstVal; \ + dst += outputStep; \ + src += inputStep; + + while (dst + 16 * outputStep <= iterEnd) { + LEFT_ITER; LEFT_ITER; LEFT_ITER; LEFT_ITER; + LEFT_ITER; LEFT_ITER; LEFT_ITER; LEFT_ITER; + LEFT_ITER; LEFT_ITER; LEFT_ITER; LEFT_ITER; + LEFT_ITER; LEFT_ITER; LEFT_ITER; LEFT_ITER; + } + while (dst < iterEnd) { + LEFT_ITER; + } + + // Process all pixels between splitLeft and splitRight. + iterEnd = &aOutput[splitRight * outputStep]; + if (boxSize <= aWidth) { + // The filter window is smaller than the row size, so the leftmost and rightmost + // samples are both within row bounds. + src = &aInput[(splitLeft - aLeftLobe) * inputStep]; + int32_t boxStep = boxSize * inputStep; + + #define CENTER_ITER \ + *dst = (alphaSum * reciprocal) >> 24; \ + alphaSum += src[boxStep] - *src; \ + dst += outputStep; \ + src += inputStep; + + while (dst + 16 * outputStep <= iterEnd) { + CENTER_ITER; CENTER_ITER; CENTER_ITER; CENTER_ITER; + CENTER_ITER; CENTER_ITER; CENTER_ITER; CENTER_ITER; + CENTER_ITER; CENTER_ITER; CENTER_ITER; CENTER_ITER; + CENTER_ITER; CENTER_ITER; CENTER_ITER; CENTER_ITER; } - uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); - - for (int32_t y = 0; y < aRows; y++) { - // Check whether the skip rect intersects this row. If the skip - // rect covers the whole surface in this row, we can avoid - // this row entirely (and any others along the skip rect). - bool inSkipRectY = y >= aSkipRect.y && - y < aSkipRect.YMost(); - if (inSkipRectY && skipRectCoversWholeRow) { - y = aSkipRect.YMost() - 1; - continue; - } - - uint32_t alphaSum = 0; - for (int32_t i = 0; i < boxSize; i++) { - int32_t pos = i - aLeftLobe; - // See assertion above; if aWidth is zero, then we would have no - // valid position to clamp to. - pos = max(pos, 0); - pos = min(pos, aWidth - 1); - alphaSum += aInput[aWidth * y + pos]; - } - for (int32_t x = 0; x < aWidth; x++) { - // Check whether we are within the skip rect. If so, go - // to the next point outside the skip rect. - if (inSkipRectY && x >= aSkipRect.x && - x < aSkipRect.XMost()) { - x = aSkipRect.XMost(); - if (x >= aWidth) - break; - - // Recalculate the neighbouring alpha values for - // our new point on the surface. - alphaSum = 0; - for (int32_t i = 0; i < boxSize; i++) { - int32_t pos = x + i - aLeftLobe; - // See assertion above; if aWidth is zero, then we would have no - // valid position to clamp to. - pos = max(pos, 0); - pos = min(pos, aWidth - 1); - alphaSum += aInput[aWidth * y + pos]; - } - } - int32_t tmp = x - aLeftLobe; - int32_t last = max(tmp, 0); - int32_t next = min(tmp + boxSize, aWidth - 1); - - aOutput[aWidth * y + x] = (uint64_t(alphaSum) * reciprocal) >> 32; - - alphaSum += aInput[aWidth * y + next] - - aInput[aWidth * y + last]; - } + while (dst < iterEnd) { + CENTER_ITER; } + } else { + // The filter window is larger than the row size, and we're in the area of split + // overlap. So the leftmost and rightmost samples are both out of bounds and need + // to be clamped. We can just precompute the difference here consequently. + int32_t firstLastDiff = aInput[(aWidth -1) * inputStep] - aInput[0]; + while (dst < iterEnd) { + *dst = (alphaSum * reciprocal) >> 24; + alphaSum += firstLastDiff; + dst += outputStep; + } + } + + // Process all remaining pixels after splitRight that would sample after the row end. + iterEnd = &aOutput[aEnd * outputStep]; + src = &aInput[(splitRight - aLeftLobe) * inputStep]; + uint8_t lastVal = aInput[(aWidth - 1) * inputStep]; + + #define RIGHT_ITER \ + *dst = (alphaSum * reciprocal) >> 24; \ + alphaSum += lastVal - *src; \ + dst += outputStep; \ + src += inputStep; + + while (dst + 16 * outputStep <= iterEnd) { + RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; + RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; + RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; + RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; RIGHT_ITER; + } + while (dst < iterEnd) { + RIGHT_ITER; + } } /** - * Identical to BoxBlurHorizontal, except it blurs top and bottom instead of - * left and right. - * XXX shouldn't we pass stride in separately here? + * Box blur involves looking at one pixel, and setting its value to the average + * of its neighbouring pixels. This is meant to provide a 3-pass approximation of a + * Gaussian blur. + * @param aTranspose Whether to transpose the buffer when reading and writing to it. + * @param aData The buffer to be blurred. + * @param aLobes The number of pixels to blend on the left and right for each of 3 passes. + * @param aWidth The number of columns in the buffers. + * @param aRows The number of rows in the buffers. + * @param aStride The stride of the buffer. */ +template static void -BoxBlurVertical(unsigned char* aInput, - unsigned char* aOutput, - int32_t aTopLobe, - int32_t aBottomLobe, - int32_t aWidth, - int32_t aRows, - const IntRect& aSkipRect) +BoxBlur(uint8_t* aData, + const int32_t aLobes[3][2], + int32_t aWidth, + int32_t aRows, + int32_t aStride, + IntRect aSkipRect) { - MOZ_ASSERT(aRows > 0); + if (aTranspose) { + swap(aWidth, aRows); + swap(aSkipRect.x, aSkipRect.y); + swap(aSkipRect.width, aSkipRect.height); + } - int32_t boxSize = aTopLobe + aBottomLobe + 1; - bool skipRectCoversWholeColumn = 0 >= aSkipRect.y && - aRows <= aSkipRect.YMost(); - if (boxSize == 1) { - memcpy(aOutput, aInput, aWidth*aRows); - return; + MOZ_ASSERT(aWidth > 0); + + // All three passes of the box blur that approximate the Gaussian are done + // on each row in turn, so we only need two temporary row buffers to process + // each row, instead of a full-sized buffer. Data moves from the source to the + // first temporary, from the first temporary to the second, then from the second + // back to the destination. This way is more cache-friendly than processing whe + // whole buffer in each pass and thus yields a nice speedup. + uint8_t* tmpRow = new (std::nothrow) uint8_t[2 * aWidth]; + if (!tmpRow) { + return; + } + uint8_t* tmpRow2 = tmpRow + aWidth; + + const int32_t stride = aTranspose ? 1 : aStride; + bool skipRectCoversWholeRow = 0 >= aSkipRect.x && + aWidth <= aSkipRect.XMost(); + + for (int32_t y = 0; y < aRows; y++) { + // Check whether the skip rect intersects this row. If the skip + // rect covers the whole surface in this row, we can avoid + // this row entirely (and any others along the skip rect). + bool inSkipRectY = y >= aSkipRect.y && + y < aSkipRect.YMost(); + if (inSkipRectY && skipRectCoversWholeRow) { + aData += stride * (aSkipRect.YMost() - y); + y = aSkipRect.YMost() - 1; + continue; } - uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); - for (int32_t x = 0; x < aWidth; x++) { - bool inSkipRectX = x >= aSkipRect.x && - x < aSkipRect.XMost(); - if (inSkipRectX && skipRectCoversWholeColumn) { - x = aSkipRect.XMost() - 1; - continue; - } + // Read in data from the source transposed if necessary. + BoxBlurRow(aData, tmpRow, aLobes[0][0], aLobes[0][1], aWidth, aStride, 0, aWidth); - uint32_t alphaSum = 0; - for (int32_t i = 0; i < boxSize; i++) { - int32_t pos = i - aTopLobe; - // See assertion above; if aRows is zero, then we would have no - // valid position to clamp to. - pos = max(pos, 0); - pos = min(pos, aRows - 1); - alphaSum += aInput[aWidth * pos + x]; - } - for (int32_t y = 0; y < aRows; y++) { - if (inSkipRectX && y >= aSkipRect.y && - y < aSkipRect.YMost()) { - y = aSkipRect.YMost(); - if (y >= aRows) - break; + // For the middle pass, the data is already pre-transposed and does not need to be post-transposed yet. + BoxBlurRow(tmpRow, tmpRow2, aLobes[1][0], aLobes[1][1], aWidth, aStride, 0, aWidth); - alphaSum = 0; - for (int32_t i = 0; i < boxSize; i++) { - int32_t pos = y + i - aTopLobe; - // See assertion above; if aRows is zero, then we would have no - // valid position to clamp to. - pos = max(pos, 0); - pos = min(pos, aRows - 1); - alphaSum += aInput[aWidth * pos + x]; - } - } - int32_t tmp = y - aTopLobe; - int32_t last = max(tmp, 0); - int32_t next = min(tmp + boxSize, aRows - 1); - - aOutput[aWidth * y + x] = (uint64_t(alphaSum) * reciprocal) >> 32; - - alphaSum += aInput[aWidth * next + x] - - aInput[aWidth * last + x]; - } + // Write back data to the destination transposed if necessary too. + // Make sure not to overwrite the skip rect by only outputting to the + // destination before and after the skip rect, if requested. + int32_t skipStart = inSkipRectY ? min(max(aSkipRect.x, 0), aWidth) : aWidth; + int32_t skipEnd = max(skipStart, aSkipRect.XMost()); + if (skipStart > 0) { + BoxBlurRow(tmpRow2, aData, aLobes[2][0], aLobes[2][1], aWidth, aStride, 0, skipStart); } + if (skipEnd < aWidth) { + BoxBlurRow(tmpRow2, aData, aLobes[2][0], aLobes[2][1], aWidth, aStride, skipEnd, aWidth); + } + + aData += stride; + } + + delete[] tmpRow; } static void ComputeLobes(int32_t aRadius, int32_t aLobes[3][2]) @@ -226,8 +335,8 @@ static void ComputeLobes(int32_t aRadius, int32_t aLobes[3][2]) } static void -SpreadHorizontal(unsigned char* aInput, - unsigned char* aOutput, +SpreadHorizontal(uint8_t* aInput, + uint8_t* aOutput, int32_t aRadius, int32_t aWidth, int32_t aRows, @@ -274,8 +383,8 @@ SpreadHorizontal(unsigned char* aInput, } static void -SpreadVertical(unsigned char* aInput, - unsigned char* aOutput, +SpreadVertical(uint8_t* aInput, + uint8_t* aOutput, int32_t aRadius, int32_t aWidth, int32_t aRows, @@ -480,7 +589,7 @@ AlphaBoxBlur::Blur(uint8_t* aData) if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) { // No need to use CheckedInt here - we have validated it in the constructor. size_t szB = stride * size.height; - unsigned char* tmpData = new (std::nothrow) uint8_t[szB]; + uint8_t* tmpData = new (std::nothrow) uint8_t[szB]; if (!tmpData) { return; @@ -488,8 +597,8 @@ AlphaBoxBlur::Blur(uint8_t* aData) memset(tmpData, 0, szB); - SpreadHorizontal(aData, tmpData, mSpreadRadius.width, GetSize().width, GetSize().height, stride, mSkipRect); - SpreadVertical(tmpData, aData, mSpreadRadius.height, GetSize().width, GetSize().height, stride, mSkipRect); + SpreadHorizontal(aData, tmpData, mSpreadRadius.width, size.width, size.height, stride, mSkipRect); + SpreadVertical(tmpData, aData, mSpreadRadius.height, size.width, size.height, stride, mSkipRect); delete [] tmpData; } @@ -508,39 +617,12 @@ AlphaBoxBlur::Blur(uint8_t* aData) if ((integralImageSize.width * integralImageSize.height) > (1 << 24)) { // Fallback to old blurring code when the surface is so large it may // overflow our integral image! - - // No need to use CheckedInt here - we have validated it in the constructor. - size_t szB = stride * size.height; - uint8_t* tmpData = new (std::nothrow) uint8_t[szB]; - if (!tmpData) { - return; - } - - memset(tmpData, 0, szB); - - uint8_t* a = aData; - uint8_t* b = tmpData; if (mBlurRadius.width > 0) { - BoxBlurHorizontal(a, b, horizontalLobes[0][0], horizontalLobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(b, a, horizontalLobes[1][0], horizontalLobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurHorizontal(a, b, horizontalLobes[2][0], horizontalLobes[2][1], stride, GetSize().height, mSkipRect); - } else { - a = tmpData; - b = aData; + BoxBlur(aData, horizontalLobes, size.width, size.height, stride, mSkipRect); } - // The result is in 'b' here. if (mBlurRadius.height > 0) { - BoxBlurVertical(b, a, verticalLobes[0][0], verticalLobes[0][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(a, b, verticalLobes[1][0], verticalLobes[1][1], stride, GetSize().height, mSkipRect); - BoxBlurVertical(b, a, verticalLobes[2][0], verticalLobes[2][1], stride, GetSize().height, mSkipRect); - } else { - a = b; + BoxBlur(aData, verticalLobes, size.width, size.height, stride, mSkipRect); } - // The result is in 'a' here. - if (a == tmpData) { - memcpy(aData, tmpData, szB); - } - delete [] tmpData; } else { size_t integralImageStride = GetAlignedStride<16>(integralImageSize.width, 4); if (integralImageStride == 0) { From f1ccc95daf43be0f56ce074f5546fa9e19017e36 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 30 Oct 2023 14:29:54 +0100 Subject: [PATCH 06/10] Issue #2364 - Only use HWA AlphaBoxBlur when targets are large enough. There's considerable cost associated with creating HWA draw targets which would negate and regress the performance won by it if used for too small targets (like small spans of text). This only uses HWA if >= 8K SurfaceAllocationSize to balance HWA draw target cost with box blur cost. --- gfx/thebes/gfxBlur.cpp | 5 +++++ layout/reftests/box-shadow/reftest.list | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gfx/thebes/gfxBlur.cpp b/gfx/thebes/gfxBlur.cpp index 77003d3f9a..63e261a9ec 100644 --- a/gfx/thebes/gfxBlur.cpp +++ b/gfx/thebes/gfxBlur.cpp @@ -75,7 +75,12 @@ gfxAlphaBoxBlur::InitDrawTarget(const DrawTarget* aReferenceDT, // Check if the backend has an accelerated DrawSurfaceWithShadow. // Currently, only D2D1.1 supports this. // Otherwise, DrawSurfaceWithShadow only supports square blurs without spread. + // When blurring small draw targets such as short spans of text, the cost of + // creating and flushing an accelerated draw target exceeds the gains from + // the faster HWA blur, so we also make sure the blurred data exceeds + // a sufficient number of pixels before HWA kicks in to offset this cost. if (aBlurRadius.IsSquare() && aSpreadRadius.IsEmpty() && + blurDataSize >= 8192 && backend == BackendType::DIRECT2D1_1) { mAccelerated = true; mDrawTarget = diff --git a/layout/reftests/box-shadow/reftest.list b/layout/reftests/box-shadow/reftest.list index 0c3bb1d38a..a195dcf430 100644 --- a/layout/reftests/box-shadow/reftest.list +++ b/layout/reftests/box-shadow/reftest.list @@ -26,7 +26,7 @@ fuzzy-if(OSX==1010,1,24) fuzzy-if(d2d,16,908) == boxshadow-large-border-radius.h fuzzy(3,500) fuzzy-if(d2d,2,1080) == boxshadow-border-radius-int.html boxshadow-border-radius-int-ref.html == boxshadow-inset-neg-spread.html about:blank == boxshadow-inset-neg-spread2.html boxshadow-inset-neg-spread2-ref.html -fuzzy(26,3610) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264 +fuzzy(26,3610) fuzzy-if(d2d,26,5910) == boxshadow-rotated.html boxshadow-rotated-ref.html # Bug 1211264 == boxshadow-inset-large-border-radius.html boxshadow-inset-large-border-radius-ref.html # fuzzy due to blur going inside, but as long as it's essentially black instead of a light gray its ok. From 3bdb386b6e15242fcb0f7ed973e535de48cd307e Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 30 Oct 2023 16:22:02 +0100 Subject: [PATCH 07/10] Fix a bug in SizeTyped templating A second template parameter 'F' was previously added to SizeTyped, but was not correspondingly added to the argument passed for the 'Sub' parameter of BaseSize. --- gfx/2d/Point.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gfx/2d/Point.h b/gfx/2d/Point.h index f66967622f..96b2d8d5cf 100644 --- a/gfx/2d/Point.h +++ b/gfx/2d/Point.h @@ -285,7 +285,7 @@ typedef IntSizeTyped IntSize; template struct SizeTyped : - public BaseSize< F, SizeTyped >, + public BaseSize< F, SizeTyped >, public units { static_assert(IsPixel::value, "'units' must be a coordinate system tag"); From d4cdecec81c4b179513b76e098ae53361612430e Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 30 Oct 2023 16:25:29 +0100 Subject: [PATCH 08/10] Add Min() and Max() convenience functions for 2d point types. This will return (min(x1,x2), min(y1,y2)) and equivalent max from 2 passed-in (x1,y1) (x2,y2) points --- gfx/2d/BaseSize.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gfx/2d/BaseSize.h b/gfx/2d/BaseSize.h index 7bcccc6299..f9f3fa4615 100644 --- a/gfx/2d/BaseSize.h +++ b/gfx/2d/BaseSize.h @@ -6,6 +6,7 @@ #ifndef MOZILLA_GFX_BASESIZE_H_ #define MOZILLA_GFX_BASESIZE_H_ +#include #include "mozilla/Attributes.h" namespace mozilla { @@ -92,6 +93,16 @@ struct BaseSize { Sub operator/(const Sub& aSize) const { return Sub(width / aSize.width, height / aSize.height); } + + friend Sub Min(const Sub& aA, const Sub& aB) { + return Sub(std::min(aA.width, aB.width), + std::min(aA.height, aB.height)); + } + + friend Sub Max(const Sub& aA, const Sub& aB) { + return Sub(std::max(aA.width, aB.width), + std::max(aA.height, aB.height)); + } }; } // namespace gfx From 0db18745f42e6b159851fc40998a8e5b557e7c2f Mon Sep 17 00:00:00 2001 From: Moonchild Date: Mon, 30 Oct 2023 16:43:13 +0100 Subject: [PATCH 09/10] Issue #2364 - Limit the growth of scaling for animated nsDisplayTransform in the fallback case. This ensures we don't scale the underlying size of the layer beyond what is close to the current display size. When box shadows get much larger than this, they start taking so much time to render that successive frames grow in scale too fast for any inter-frame reuse to be possible. With this, we avoid that and no longer get crushed by re-rendering gigantic box shadows every single frame. See BZ 1383825 --- gfx/thebes/gfxUtils.cpp | 13 +++++---- gfx/thebes/gfxUtils.h | 5 ++-- layout/base/FrameLayerBuilder.cpp | 45 +++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index 7202b10ddf..903ba49e28 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -692,7 +692,7 @@ gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) } /*static*/ gfxFloat -gfxUtils::ClampToScaleFactor(gfxFloat aVal) +gfxUtils::ClampToScaleFactor(gfxFloat aVal, bool aRoundDown) { // Arbitary scale factor limitation. We can increase this // for better scaling performance at the cost of worse @@ -713,14 +713,17 @@ gfxUtils::ClampToScaleFactor(gfxFloat aVal) gfxFloat power = log(aVal)/log(kScaleResolution); - // If power is within 1e-5 of an integer, round to nearest to - // prevent floating point errors, otherwise round up to the - // next integer value. if (fabs(power - NS_round(power)) < 1e-5) { + // If power is within 1e-5 of an integer, round to nearest to + // prevent floating point errors. power = NS_round(power); - } else if (inverse) { + } else if (inverse != aRoundDown) { + // Use floor when we are either inverted or rounding down, but + // not both. power = floor(power); } else { + // Otherwise, ceil when we are not inverted and not rounding + // down, or we are inverted and rounding down. power = ceil(power); } diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index 80094a762f..e2b5d476f5 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -128,9 +128,10 @@ public: /** * Return the smallest power of kScaleResolution (2) greater than or equal to - * aVal. + * aVal. If aRoundDown is specified, the power of 2 will be less than + * or equal to aVal. */ - static gfxFloat ClampToScaleFactor(gfxFloat aVal); + static gfxFloat ClampToScaleFactor(gfxFloat aVal, bool aRoundDown = false); /** * Clears surface to aColor (which defaults to transparent black). diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index d38b17adc4..d23ca436c9 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -5245,6 +5245,23 @@ static void RestrictScaleToMaxLayerSize(gfxSize& aScale, aScale.height /= scale; } } + +static nsSize +ComputeDesiredDisplaySizeForAnimation(nsIFrame* aContainerFrame) +{ + // Use the size of the nearest widget as the maximum size. This + // is important since it might be a popup that is bigger than the + // pres context's size. + nsPresContext* presContext = aContainerFrame->PresContext(); + nsIWidget* widget = aContainerFrame->GetNearestWidget(); + if (widget) { + return LayoutDevicePixel::ToAppUnits(widget->GetClientSize(), + presContext->AppUnitsPerDevPixel()); + } else { + return presContext->GetVisibleArea().Size(); + } +} + static bool ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder, nsDisplayListBuilder* aDisplayListBuilder, @@ -5309,20 +5326,7 @@ ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder, aContainerItem->GetType() == nsDisplayItem::TYPE_TRANSFORM && EffectCompositor::HasAnimationsForCompositor( aContainerFrame, eCSSProperty_transform)) { - // Use the size of the nearest widget as the maximum size. This - // is important since it might be a popup that is bigger than the - // pres context's size. - nsPresContext* presContext = aContainerFrame->PresContext(); - nsIWidget* widget = aContainerFrame->GetNearestWidget(); - nsSize displaySize; - if (widget) { - LayoutDeviceIntSize widgetSize = widget->GetClientSize(); - int32_t p2a = presContext->AppUnitsPerDevPixel(); - displaySize.width = NSIntPixelsToAppUnits(widgetSize.width, p2a); - displaySize.height = NSIntPixelsToAppUnits(widgetSize.height, p2a); - } else { - displaySize = presContext->GetVisibleArea().Size(); - } + nsSize displaySize = ComputeDesiredDisplaySizeForAnimation(aContainerFrame); // compute scale using the animation on the container (ignoring // its ancestors) scale = nsLayoutUtils::ComputeSuitableScaleForAnimation( @@ -5359,6 +5363,19 @@ ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder, if (clamp) { scale.width = gfxUtils::ClampToScaleFactor(scale.width); scale.height = gfxUtils::ClampToScaleFactor(scale.height); + + // Limit animated scale factors to not grow excessively beyond the display size. + nsSize maxScale(4, 4); + if (!aVisibleRect.IsEmpty()) { + nsSize displaySize = ComputeDesiredDisplaySizeForAnimation(aContainerFrame); + maxScale = Max(maxScale, displaySize / aVisibleRect.Size()); + } + if (scale.width > maxScale.width) { + scale.width = gfxUtils::ClampToScaleFactor(maxScale.width, true); + } + if (scale.height > maxScale.height) { + scale.height = gfxUtils::ClampToScaleFactor(maxScale.height, true); + } } } else { // XXX Do we need to move nearly-integer values to integers here? From 17f1bcac4c4d0b4f6f9a5b29acf046459aedac13 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Wed, 1 Nov 2023 14:27:24 +0100 Subject: [PATCH 10/10] Make ECMA v6 the default for devtools pretty-printing. --- devtools/client/debugger/new/pretty-print-worker.js | 2 +- devtools/shared/acorn/acorn.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devtools/client/debugger/new/pretty-print-worker.js b/devtools/client/debugger/new/pretty-print-worker.js index 4fa735e16e..cd9cea1726 100644 --- a/devtools/client/debugger/new/pretty-print-worker.js +++ b/devtools/client/debugger/new/pretty-print-worker.js @@ -1068,7 +1068,7 @@ var Debugger = // be either 3, or 5, or 6. This influences support for strict // mode, the set of reserved words, support for getters and // setters and other features. - ecmaVersion: 5, + ecmaVersion: 6, // Turn on `strictSemicolons` to prevent the parser from doing // automatic semicolon insertion. strictSemicolons: false, diff --git a/devtools/shared/acorn/acorn.js b/devtools/shared/acorn/acorn.js index 55d9a1968e..53dafa2ca6 100644 --- a/devtools/shared/acorn/acorn.js +++ b/devtools/shared/acorn/acorn.js @@ -1299,7 +1299,7 @@ var defaultOptions = { // be either 3, or 5, or 6. This influences support for strict // mode, the set of reserved words, support for getters and // setters and other features. - ecmaVersion: 5, + ecmaVersion: 6, // Source type ("script" or "module") for different semantics sourceType: "script", // `onInsertedSemicolon` can be a callback that will be called