mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-18 23:15:38 +00:00
Bug 940845 - Part 6: Cache blurs when possible. r=Bas
This commit is contained in:
parent
4118b653d4
commit
cb06cb865b
@ -10,7 +10,10 @@
|
||||
|
||||
#include "mozilla/gfx/Blur.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsExpirationTracker.h"
|
||||
#include "nsClassHashtable.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
gfxAlphaBoxBlur::gfxAlphaBoxBlur()
|
||||
@ -87,52 +90,71 @@ gfxAlphaBoxBlur::Init(const gfxRect& aRect,
|
||||
return mContext;
|
||||
}
|
||||
|
||||
void
|
||||
DrawBlur(gfxContext* aDestinationCtx,
|
||||
SourceSurface* aBlur,
|
||||
const IntPoint& aTopLeft,
|
||||
const Rect* aDirtyRect)
|
||||
{
|
||||
DrawTarget *dest = aDestinationCtx->GetDrawTarget();
|
||||
|
||||
nsRefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern();
|
||||
Pattern* pat = thebesPat->GetPattern(dest, nullptr);
|
||||
|
||||
Matrix oldTransform = dest->GetTransform();
|
||||
Matrix newTransform = oldTransform;
|
||||
newTransform.Translate(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();
|
||||
}
|
||||
}
|
||||
|
||||
TemporaryRef<SourceSurface>
|
||||
gfxAlphaBoxBlur::DoBlur(DrawTarget* aDT, IntPoint* aTopLeft)
|
||||
{
|
||||
mBlur->Blur(mData);
|
||||
|
||||
*aTopLeft = mBlur->GetRect().TopLeft();
|
||||
|
||||
return aDT->CreateSourceSurfaceFromData(mData,
|
||||
mBlur->GetSize(),
|
||||
mBlur->GetStride(),
|
||||
SurfaceFormat::A8);
|
||||
}
|
||||
|
||||
void
|
||||
gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx)
|
||||
{
|
||||
if (!mContext)
|
||||
return;
|
||||
|
||||
mBlur->Blur(mData);
|
||||
|
||||
mozilla::gfx::Rect* dirtyRect = mBlur->GetDirtyRect();
|
||||
|
||||
DrawTarget *dest = aDestinationCtx->GetDrawTarget();
|
||||
if (!dest) {
|
||||
NS_WARNING("Blurring not supported for Thebes contexts!");
|
||||
return;
|
||||
}
|
||||
|
||||
mozilla::RefPtr<SourceSurface> mask
|
||||
= dest->CreateSourceSurfaceFromData(mData,
|
||||
mBlur->GetSize(),
|
||||
mBlur->GetStride(),
|
||||
SurfaceFormat::A8);
|
||||
Rect* dirtyRect = mBlur->GetDirtyRect();
|
||||
|
||||
IntPoint topLeft;
|
||||
RefPtr<SourceSurface> mask = DoBlur(dest, &topLeft);
|
||||
if (!mask) {
|
||||
NS_ERROR("Failed to create mask!");
|
||||
return;
|
||||
}
|
||||
|
||||
nsRefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern();
|
||||
Pattern* pat = thebesPat->GetPattern(dest, nullptr);
|
||||
|
||||
Matrix oldTransform = dest->GetTransform();
|
||||
Matrix newTransform = oldTransform;
|
||||
newTransform.Translate(mBlur->GetRect().x, mBlur->GetRect().y);
|
||||
|
||||
// Avoid a semi-expensive clip operation if we can, otherwise
|
||||
// clip to the dirty rect
|
||||
if (dirtyRect) {
|
||||
dest->PushClipRect(*dirtyRect);
|
||||
}
|
||||
|
||||
dest->SetTransform(newTransform);
|
||||
dest->MaskSurface(*pat, mask, Point(0, 0));
|
||||
dest->SetTransform(oldTransform);
|
||||
|
||||
if (dirtyRect) {
|
||||
dest->PopClip();
|
||||
}
|
||||
DrawBlur(aDestinationCtx, mask, topLeft, dirtyRect);
|
||||
}
|
||||
|
||||
gfxIntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd)
|
||||
@ -142,6 +164,204 @@ gfxIntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd)
|
||||
return gfxIntSize(size.width, size.height);
|
||||
}
|
||||
|
||||
struct BlurCacheKey : public PLDHashEntryHdr {
|
||||
typedef const BlurCacheKey& KeyType;
|
||||
typedef const BlurCacheKey* KeyTypePointer;
|
||||
enum { ALLOW_MEMMOVE = true };
|
||||
|
||||
gfxRect mRect;
|
||||
gfxIntSize mBlurRadius;
|
||||
gfxRect mSkipRect;
|
||||
BackendType mBackend;
|
||||
|
||||
BlurCacheKey(const gfxRect& aRect, const gfxIntSize &aBlurRadius, const gfxRect& aSkipRect, BackendType aBackend)
|
||||
: mRect(aRect)
|
||||
, mBlurRadius(aBlurRadius)
|
||||
, mSkipRect(aSkipRect)
|
||||
, mBackend(aBackend)
|
||||
{ }
|
||||
|
||||
BlurCacheKey(const BlurCacheKey* aOther)
|
||||
: mRect(aOther->mRect)
|
||||
, mBlurRadius(aOther->mBlurRadius)
|
||||
, mSkipRect(aOther->mSkipRect)
|
||||
, mBackend(aOther->mBackend)
|
||||
{ }
|
||||
|
||||
static PLDHashNumber
|
||||
HashKey(const KeyTypePointer aKey)
|
||||
{
|
||||
PLDHashNumber hash = HashBytes(&aKey->mRect.x, 4 * sizeof(gfxFloat));
|
||||
hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height);
|
||||
hash = AddToHash(hash, HashBytes(&aKey->mSkipRect.x, 4 * sizeof(gfxFloat)));
|
||||
hash = AddToHash(hash, (uint32_t)aKey->mBackend);
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool KeyEquals(KeyTypePointer aKey) const
|
||||
{
|
||||
if (aKey->mRect.IsEqualInterior(mRect) &&
|
||||
aKey->mBlurRadius == mBlurRadius &&
|
||||
aKey->mSkipRect.IsEqualInterior(mSkipRect) &&
|
||||
aKey->mBackend == mBackend) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static KeyTypePointer KeyToPointer(KeyType aKey)
|
||||
{
|
||||
return &aKey;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is what is cached. It need to be allocated in an object separated
|
||||
* to the cache entry to be able to be tracked by the nsExpirationTracker.
|
||||
* */
|
||||
struct BlurCacheData {
|
||||
BlurCacheData(SourceSurface* aBlur, const IntPoint& aTopLeft, const gfxRect& aDirtyRect, const BlurCacheKey& aKey)
|
||||
: mBlur(aBlur)
|
||||
, mTopLeft(aTopLeft)
|
||||
, mDirtyRect(aDirtyRect)
|
||||
, mKey(aKey)
|
||||
{}
|
||||
|
||||
BlurCacheData(const BlurCacheData& aOther)
|
||||
: mBlur(aOther.mBlur)
|
||||
, mTopLeft(aOther.mTopLeft)
|
||||
, mDirtyRect(aOther.mDirtyRect)
|
||||
, mKey(aOther.mKey)
|
||||
{ }
|
||||
|
||||
nsExpirationState *GetExpirationState() {
|
||||
return &mExpirationState;
|
||||
}
|
||||
|
||||
nsExpirationState mExpirationState;
|
||||
RefPtr<SourceSurface> mBlur;
|
||||
IntPoint mTopLeft;
|
||||
gfxRect mDirtyRect;
|
||||
BlurCacheKey mKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class implements a cache with no maximum size, that retains the
|
||||
* SourceSurfaces used to draw the blurs.
|
||||
*
|
||||
* An entry stays in the cache as long as it is used often.
|
||||
*/
|
||||
class BlurCache MOZ_FINAL : public nsExpirationTracker<BlurCacheData,4>
|
||||
{
|
||||
public:
|
||||
BlurCache()
|
||||
: nsExpirationTracker<BlurCacheData, 4>(GENERATION_MS)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void NotifyExpired(BlurCacheData* aObject)
|
||||
{
|
||||
RemoveObject(aObject);
|
||||
mHashEntries.Remove(aObject->mKey);
|
||||
}
|
||||
|
||||
BlurCacheData* Lookup(const gfxRect& aRect,
|
||||
const gfxIntSize& aBlurRadius,
|
||||
const gfxRect& aSkipRect,
|
||||
BackendType aBackendType,
|
||||
const gfxRect* aDirtyRect)
|
||||
{
|
||||
BlurCacheData* blur =
|
||||
mHashEntries.Get(BlurCacheKey(aRect, aBlurRadius, aSkipRect, aBackendType));
|
||||
|
||||
if (blur) {
|
||||
if (aDirtyRect && !blur->mDirtyRect.Contains(*aDirtyRect)) {
|
||||
return nullptr;
|
||||
}
|
||||
MarkUsed(blur);
|
||||
}
|
||||
|
||||
return blur;
|
||||
}
|
||||
|
||||
// Returns true if we successfully register the blur in the cache, false
|
||||
// otherwise.
|
||||
bool RegisterEntry(BlurCacheData* aValue)
|
||||
{
|
||||
nsresult rv = AddObject(aValue);
|
||||
if (NS_FAILED(rv)) {
|
||||
// We are OOM, and we cannot track this object. We don't want stall
|
||||
// entries in the hash table (since the expiration tracker is responsible
|
||||
// for removing the cache entries), so we avoid putting that entry in the
|
||||
// table, which is a good things considering we are short on memory
|
||||
// anyway, we probably don't want to retain things.
|
||||
return false;
|
||||
}
|
||||
mHashEntries.Put(aValue->mKey, aValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
static const uint32_t GENERATION_MS = 1000;
|
||||
/**
|
||||
* FIXME use nsTHashtable to avoid duplicating the BlurCacheKey.
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
|
||||
*/
|
||||
nsClassHashtable<BlurCacheKey, BlurCacheData> mHashEntries;
|
||||
};
|
||||
|
||||
static BlurCache* gBlurCache = nullptr;
|
||||
|
||||
SourceSurface*
|
||||
GetCachedBlur(DrawTarget *aDT,
|
||||
const gfxRect& aRect,
|
||||
const gfxIntSize& aBlurRadius,
|
||||
const gfxRect& aSkipRect,
|
||||
const gfxRect& aDirtyRect,
|
||||
IntPoint* aTopLeft)
|
||||
{
|
||||
if (!gBlurCache) {
|
||||
gBlurCache = new BlurCache();
|
||||
}
|
||||
BlurCacheData* cached = gBlurCache->Lookup(aRect, aBlurRadius, aSkipRect, aDT->GetType(), &aDirtyRect);
|
||||
if (cached) {
|
||||
*aTopLeft = cached->mTopLeft;
|
||||
return cached->mBlur;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
CacheBlur(DrawTarget *aDT,
|
||||
const gfxRect& aRect,
|
||||
const gfxIntSize& aBlurRadius,
|
||||
const gfxRect& aSkipRect,
|
||||
SourceSurface* aBlur,
|
||||
const IntPoint& aTopLeft,
|
||||
const gfxRect& aDirtyRect)
|
||||
{
|
||||
// If we already had a cached value with this key, but an incorrect dirty region then just update
|
||||
// the existing entry
|
||||
if (BlurCacheData* cached = gBlurCache->Lookup(aRect, aBlurRadius, aSkipRect, aDT->GetType(), nullptr)) {
|
||||
cached->mBlur = aBlur;
|
||||
cached->mTopLeft = aTopLeft;
|
||||
cached->mDirtyRect = aDirtyRect;
|
||||
return;
|
||||
}
|
||||
|
||||
BlurCacheKey key(aRect, aBlurRadius, aSkipRect, aDT->GetType());
|
||||
BlurCacheData* data = new BlurCacheData(aBlur, aTopLeft, aDirtyRect, key);
|
||||
if (!gBlurCache->RegisterEntry(data)) {
|
||||
delete data;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gfxAlphaBoxBlur::ShutdownBlurCache()
|
||||
{
|
||||
delete gBlurCache;
|
||||
gBlurCache = nullptr;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
gfxAlphaBoxBlur::BlurRectangle(gfxContext *aDestinationCtx,
|
||||
const gfxRect& aRect,
|
||||
@ -152,26 +372,41 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContext *aDestinationCtx,
|
||||
const gfxRect& aSkipRect)
|
||||
{
|
||||
gfxIntSize blurRadius = CalculateBlurRadius(aBlurStdDev);
|
||||
|
||||
// Create the temporary surface for blurring
|
||||
gfxAlphaBoxBlur blur;
|
||||
gfxContext *dest = blur.Init(aRect, gfxIntSize(), blurRadius, &aDirtyRect, &aSkipRect);
|
||||
|
||||
if (!dest) {
|
||||
|
||||
DrawTarget *dt = aDestinationCtx->GetDrawTarget();
|
||||
if (!dt) {
|
||||
NS_WARNING("Blurring not supported for Thebes contexts!");
|
||||
return;
|
||||
}
|
||||
|
||||
gfxRect shadowGfxRect = aRect;
|
||||
shadowGfxRect.Round();
|
||||
IntPoint topLeft;
|
||||
RefPtr<SourceSurface> surface = GetCachedBlur(dt, aRect, blurRadius, aSkipRect, aDirtyRect, &topLeft);
|
||||
if (!surface) {
|
||||
// Create the temporary surface for blurring
|
||||
gfxAlphaBoxBlur blur;
|
||||
gfxContext *dest = blur.Init(aRect, gfxIntSize(), blurRadius, &aDirtyRect, &aSkipRect);
|
||||
|
||||
dest->NewPath();
|
||||
if (aCornerRadii) {
|
||||
dest->RoundedRectangle(shadowGfxRect, *aCornerRadii);
|
||||
} else {
|
||||
dest->Rectangle(shadowGfxRect);
|
||||
if (!dest) {
|
||||
return;
|
||||
}
|
||||
|
||||
gfxRect shadowGfxRect = aRect;
|
||||
shadowGfxRect.Round();
|
||||
|
||||
dest->NewPath();
|
||||
if (aCornerRadii) {
|
||||
dest->RoundedRectangle(shadowGfxRect, *aCornerRadii);
|
||||
} else {
|
||||
dest->Rectangle(shadowGfxRect);
|
||||
}
|
||||
dest->Fill();
|
||||
|
||||
surface = blur.DoBlur(dt, &topLeft);
|
||||
CacheBlur(dt, aRect, blurRadius, aSkipRect, surface, topLeft, aDirtyRect);
|
||||
}
|
||||
dest->Fill();
|
||||
|
||||
aDestinationCtx->SetColor(aShadowColor);
|
||||
blur.Paint(aDestinationCtx);
|
||||
Rect dirtyRect(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height);
|
||||
DrawBlur(aDestinationCtx, surface, topLeft, &dirtyRect);
|
||||
}
|
||||
|
||||
|
@ -86,6 +86,8 @@ public:
|
||||
return mContext;
|
||||
}
|
||||
|
||||
mozilla::TemporaryRef<mozilla::gfx::SourceSurface> DoBlur(mozilla::gfx::DrawTarget* aDT, mozilla::gfx::IntPoint* aTopLeft);
|
||||
|
||||
/**
|
||||
* Does the actual blurring/spreading and mask applying. Users of this
|
||||
* object must have drawn whatever they want to be blurred onto the internal
|
||||
@ -129,6 +131,8 @@ public:
|
||||
const gfxRect& aDirtyRect,
|
||||
const gfxRect& aSkipRect);
|
||||
|
||||
static void ShutdownBlurCache();
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
|
@ -452,6 +452,7 @@ gfxPlatform::Shutdown()
|
||||
gfxFontCache::Shutdown();
|
||||
gfxFontGroup::Shutdown();
|
||||
gfxGradientCache::Shutdown();
|
||||
gfxAlphaBoxBlur::ShutdownBlurCache();
|
||||
gfxGraphiteShaper::Shutdown();
|
||||
#if defined(XP_MACOSX) || defined(XP_WIN) // temporary, until this is implemented on others
|
||||
gfxPlatformFontList::Shutdown();
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "gfxTypes.h"
|
||||
#include "gfxFontFamilyList.h"
|
||||
#include "gfxBlur.h"
|
||||
#include "nsRect.h"
|
||||
|
||||
#include "qcms.h"
|
||||
|
Loading…
x
Reference in New Issue
Block a user