From a38734a09b442e681df7a18a37d52d4d7dbeb6b2 Mon Sep 17 00:00:00 2001 From: "roc+@cs.cmu.edu" Date: Wed, 9 May 2007 13:33:16 -0700 Subject: [PATCH] Bug 375760. Implement new textrun cache: gfxTextRunCache (base cache which leaves textrun lifetime management to the client) and gfxGlobalTextRunCache (cache which manages textrun lifetimes using nsExpirationTracker). r=vlad --- gfx/src/thebes/nsThebesFontMetrics.h | 36 +- gfx/thebes/public/gfxTextRunCache.h | 258 +++++------ gfx/thebes/src/gfxPlatform.cpp | 6 +- gfx/thebes/src/gfxTextRunCache.cpp | 611 +++++++++++++++------------ 4 files changed, 509 insertions(+), 402 deletions(-) diff --git a/gfx/src/thebes/nsThebesFontMetrics.h b/gfx/src/thebes/nsThebesFontMetrics.h index ccbb90f0ba1..802d31ad71b 100644 --- a/gfx/src/thebes/nsThebesFontMetrics.h +++ b/gfx/src/thebes/nsThebesFontMetrics.h @@ -159,28 +159,40 @@ protected: public: AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC, const char* aString, PRInt32 aLength, PRBool aEnableSpacing) { - mTextRun = gfxTextRunCache::GetCache()->GetOrMakeTextRun( + mTextRun = gfxGlobalTextRunCache::GetTextRun( + NS_REINTERPRET_CAST(const PRUint8*, aString), aLength, + aMetrics->mFontGroup, NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)), - aMetrics->mFontGroup, aString, aLength, aMetrics->mP2A, - aMetrics->GetRightToLeftTextRunMode(), aEnableSpacing, &mOwning); + aMetrics->mP2A, + ComputeFlags(aMetrics, aEnableSpacing)); } AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC, const PRUnichar* aString, PRInt32 aLength, PRBool aEnableSpacing) { - mTextRun = gfxTextRunCache::GetCache()->GetOrMakeTextRun( + mTextRun = gfxGlobalTextRunCache::GetTextRun( + aString, aLength, aMetrics->mFontGroup, NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)), - aMetrics->mFontGroup, aString, aLength, aMetrics->mP2A, - aMetrics->GetRightToLeftTextRunMode(), aEnableSpacing, &mOwning); - } - ~AutoTextRun() { - if (mOwning) { - delete mTextRun; - } + aMetrics->mP2A, + ComputeFlags(aMetrics, aEnableSpacing)); } gfxTextRun* operator->() { return mTextRun; } gfxTextRun* get() { return mTextRun; } + private: gfxTextRun* mTextRun; - PRBool mOwning; + + static PRUint32 ComputeFlags(nsThebesFontMetrics* aMetrics, + PRBool aEnableSpacing) { + PRUint32 flags = 0; + if (aMetrics->GetRightToLeftTextRunMode()) { + flags |= gfxTextRunFactory::TEXT_IS_RTL; + } + if (aEnableSpacing) { + flags |= gfxTextRunFactory::TEXT_ENABLE_SPACING | + gfxTextRunFactory::TEXT_ABSOLUTE_SPACING | + gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING; + } + return flags; + } }; friend class AutoTextRun; diff --git a/gfx/thebes/public/gfxTextRunCache.h b/gfx/thebes/public/gfxTextRunCache.h index c3fb7f9defc..f024678463b 100644 --- a/gfx/thebes/public/gfxTextRunCache.h +++ b/gfx/thebes/public/gfxTextRunCache.h @@ -38,152 +38,158 @@ #ifndef GFX_TEXT_RUN_CACHE_H #define GFX_TEXT_RUN_CACHE_H -#include "nsRefPtrHashtable.h" -#include "nsClassHashtable.h" - #include "gfxFont.h" +#include "nsCheapSets.h" -#include "prtime.h" - +/** + * A textrun cache object. A general textrun caching solution. If you use + * this class to create a textrun cache, you are responsible for managing + * textrun lifetimes. The full power of textrun creation is exposed; you can + * set all textrun creation flags and parameters. + */ class THEBES_API gfxTextRunCache { public: - /* - * Get the global gfxTextRunCache. You must call Init() before - * calling this method. - */ - static gfxTextRunCache* GetCache() { - return mGlobalCache; + gfxTextRunCache() { + mCache.Init(100); } + ~gfxTextRunCache() {} - static nsresult Init(); - // It's OK to call Shutdown if we never actually started or we already - // shut down. - static void Shutdown(); - - /* Will return a pointer to a gfxTextRun, which may or may not be from - * the cache. If aCallerOwns is set to true, the caller owns the textrun - * and must delete it. Otherwise the returned textrun is only valid until - * the next GetOrMakeTextRun call and the caller must not delete it. + /** + * Get a textrun from the cache, create one if necessary. + * @param aFlags the flags TEXT_IS_ASCII, TEXT_IS_8BIT and TEXT_HAS_SURROGATES + * are ignored; the cache sets them based on the string. + * @param aCallerOwns if this is null, the cache always creates a new + * textrun owned by the caller. If non-null, the cache may return a textrun + * that was previously created and is owned by some previous caller + * to GetOrMakeTextRun on this cache. If so, *aCallerOwns will be set + * to false. */ - gfxTextRun *GetOrMakeTextRun (gfxContext* aContext, gfxFontGroup *aFontGroup, - const char *aString, PRUint32 aLength, - PRUint32 aAppUnitsPerDevUnit, PRBool aIsRTL, - PRBool aEnableSpacing, PRBool *aCallerOwns); - gfxTextRun *GetOrMakeTextRun (gfxContext* aContext, gfxFontGroup *aFontGroup, - const PRUnichar *aString, PRUint32 aLength, - PRUint32 aAppUnitsPerDevUnit, PRBool aIsRTL, - PRBool aEnableSpacing, PRBool *aCallerOwns); + gfxTextRun *GetOrMakeTextRun(const PRUnichar *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, + const gfxFontGroup::Parameters *aParams, + PRUint32 aFlags, PRBool *aCallerOwns = nsnull); + /** + * Get a textrun from the cache, create one if necessary. + * @param aFlags the flags TEXT_IS_ASCII, TEXT_IS_8BIT and TEXT_HAS_SURROGATES + * are ignored; the cache sets them based on the string. + * @param aCallerOwns if this is null, the cache always creates a new + * textrun owned by the caller. If non-null, the cache may return a textrun + * that was previously created and is owned by some previous caller + * to GetOrMakeTextRun on this cache. If so, *aCallerOwns will be set + * to false. + */ + gfxTextRun *GetOrMakeTextRun(const PRUint8 *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, + const gfxFontGroup::Parameters *aParams, + PRUint32 aFlags, PRBool *aCallerOwns = nsnull); + + /** + * Notify that a text run was hit in the cache, a new one created, and + * that the new one has replaced the old one in the cache. + */ + virtual void NotifyRemovedFromCache(gfxTextRun *aTextRun) {} + + /** + * Remove a textrun from the cache. This must be called before aTextRun + * is deleted! + */ + void RemoveTextRun(gfxTextRun *aTextRun); + + /** The following flags are part of the cache key: */ + enum { FLAG_MASK = + gfxTextRunFactory::TEXT_IS_RTL | + gfxTextRunFactory::TEXT_ENABLE_SPACING | + gfxTextRunFactory::TEXT_ABSOLUTE_SPACING | + gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING | + gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS | + gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX + }; protected: - gfxTextRunCache(); + struct THEBES_API CacheHashKey { + void *mFontOrGroup; + const void *mString; + PRUint32 mLength; + PRUint32 mAppUnitsPerDevUnit; + PRUint32 mFlags; + PRUint32 mStringHash; - static gfxTextRunCache *mGlobalCache; - - /* A small container class to hold a gfxFontGroup ref and a string. - * This is used as the key for the cache hash table; to avoid - * copying a whole pile of strings every time we do a hash lookup. - * we only create our own copy of the string when Realize() is called. - * gfxTextRunCache calls Realize whenever it puts a new entry into - * the hashtable. - */ - template - struct FontGroupAndStringT { - FontGroupAndStringT(gfxFontGroup *fg, const GenericString* str) - : mFontGroup(fg), mString(str) { } - - typedef typename RealString::char_type char_type; - - FontGroupAndStringT(const FontGroupAndStringT& other) - : mFontGroup(other.mFontGroup), mString(&mRealString) - { - mRealString.Assign(*other.mString); - } - - void Realize() { - mRealString.Assign(*mString); - mString = &mRealString; - } - const char_type *GetRealString() { - return mRealString.get(); - } - - nsRefPtr mFontGroup; - RealString mRealString; - const GenericString* mString; + CacheHashKey(void *aFontOrGroup, const void *aString, PRUint32 aLength, + PRUint32 aAppUnitsPerDevUnit, PRUint32 aFlags, PRUint32 aStringHash) + : mFontOrGroup(aFontOrGroup), mString(aString), mLength(aLength), + mAppUnitsPerDevUnit(aAppUnitsPerDevUnit), mFlags(aFlags), + mStringHash(aStringHash) {} }; - static PRUint32 HashDouble(const double d) { - if (d == 0.0) - return 0; - int exponent; - double mantissa = frexp (d, &exponent); - return (PRUint32) (2 * fabs(mantissa) - 1); - } + class THEBES_API CacheHashEntry : public PLDHashEntryHdr { + public: + typedef const CacheHashKey &KeyType; + typedef const CacheHashKey *KeyTypePointer; - template - struct FontGroupAndStringHashKeyT : public PLDHashEntryHdr { - typedef const T& KeyType; - typedef const T* KeyTypePointer; - - FontGroupAndStringHashKeyT(KeyTypePointer aObj) : mObj(*aObj) { } - FontGroupAndStringHashKeyT(const FontGroupAndStringHashKeyT& other) : mObj(other.mObj) { } - ~FontGroupAndStringHashKeyT() { } - - KeyType GetKey() const { return mObj; } - - PRBool KeyEquals(KeyTypePointer aKey) const { - return - mObj.mString->Equals(*(aKey->mString)) && - mObj.mFontGroup->Equals(*(aKey->mFontGroup.get())); - } + // When constructing a new entry in the hashtable, mTextRuns will be + // blank. The caller of Put() will fill it in. + CacheHashEntry(KeyTypePointer aKey) { } + CacheHashEntry(const CacheHashEntry& toCopy) { NS_ERROR("Should not be called"); } + ~CacheHashEntry() { } + PRBool KeyEquals(const KeyTypePointer aKey) const; static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } - static PLDHashNumber HashKey(KeyTypePointer aKey) { - PRUint32 h1 = HashString(*(aKey->mString)); - PRUint32 h2 = HashString(aKey->mFontGroup->GetFamilies()); - PRUint32 h3 = HashDouble(aKey->mFontGroup->GetStyle()->size); + static PLDHashNumber HashKey(const KeyTypePointer aKey); + enum { ALLOW_MEMMOVE = PR_TRUE }; - return h1 ^ h2 ^ h3; - } - enum { ALLOW_MEMMOVE = PR_FALSE }; - - private: - const T mObj; + gfxTextRun *mTextRun; }; - struct TextRunEntry { - TextRunEntry(gfxTextRun *tr) : textRun(tr), lastUse(PR_Now()) { } - void Used() { lastUse = PR_Now(); } + CacheHashKey GetKeyForTextRun(gfxTextRun *aTextRun); - gfxTextRun* textRun; - PRTime lastUse; - - ~TextRunEntry() { delete textRun; } - }; - - typedef FontGroupAndStringT FontGroupAndString; - typedef FontGroupAndStringT FontGroupAndCString; - - typedef FontGroupAndStringHashKeyT FontGroupAndStringHashKey; - typedef FontGroupAndStringHashKeyT FontGroupAndCStringHashKey; - - nsClassHashtable mHashTableUTF16; - nsClassHashtable mHashTableASCII; - - void EvictUTF16(); - void EvictASCII(); - - PRTime mLastUTF16Eviction; - PRTime mLastASCIIEviction; - - static PLDHashOperator UTF16EvictEnumerator(const FontGroupAndString& key, - nsAutoPtr &value, - void *closure); - - static PLDHashOperator ASCIIEvictEnumerator(const FontGroupAndCString& key, - nsAutoPtr &value, - void *closure); + nsTHashtable mCache; }; +/** + * A simple global textrun cache for textruns that do not carry state + * (e.g., actual or potential linebreaks) and do not need complex initialization. + * The lifetimes of these textruns are managed by the cache (they are auto-expired + * after a certain period of time). + */ +class THEBES_API gfxGlobalTextRunCache { +public: + /** + * Get a textrun for the given text, using a global cache. The returned + * textrun is valid until the next event loop. We own it, the caller + * must not free it. + * Do not set any state in the textrun (e.g. actual or potential linebreaks). + * Flags IS_8BIT, IS_ASCII and HAS_SURROGATES are automatically set + * appropriately. + * Flag IS_PERSISTENT must NOT be set unless aText is guaranteed to live + * forever. + */ + static gfxTextRun *GetTextRun(const PRUnichar *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, + gfxContext *aRefContext, + PRUint32 aAppUnitsPerDevUnit, + PRUint32 aFlags); + + /** + * Get a textrun for the given text, using a global cache. The returned + * textrun is valid until the next event loop. We own it, the caller + * must not free it. + * Do not set any state in the textrun (e.g. actual or potential linebreaks). + * Flags IS_8BIT, IS_ASCII and HAS_SURROGATES are automatically set + * appropriately. + * Flag IS_PERSISTENT must NOT be set unless aText is guaranteed to live + * forever. + */ + static gfxTextRun *GetTextRun(const PRUint8 *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, + gfxContext *aRefContext, + PRUint32 aAppUnitsPerDevUnit, + PRUint32 aFlags); + +protected: + friend class gfxPlatform; + + static nsresult Init(); + static void Shutdown(); +}; #endif /* GFX_TEXT_RUN_CACHE_H */ diff --git a/gfx/thebes/src/gfxPlatform.cpp b/gfx/thebes/src/gfxPlatform.cpp index 99ccfa62c2e..cc4a34b1981 100644 --- a/gfx/thebes/src/gfxPlatform.cpp +++ b/gfx/thebes/src/gfxPlatform.cpp @@ -108,9 +108,9 @@ gfxPlatform::Init() return rv; } - rv = gfxTextRunCache::Init(); + rv = gfxGlobalTextRunCache::Init(); if (NS_FAILED(rv)) { - NS_ERROR("Could not initialize gfxTextRunCache"); + NS_ERROR("Could not initialize gfxGlobalTextRunCache"); Shutdown(); return rv; } @@ -123,7 +123,7 @@ gfxPlatform::Shutdown() { // These may be called before the corresponding subsystems have actually // started up. That's OK, they can handle it. - gfxTextRunCache::Shutdown(); + gfxGlobalTextRunCache::Shutdown(); gfxFontCache::Shutdown(); #if defined(XP_MACOSX) gfxQuartzFontCache::Shutdown(); diff --git a/gfx/thebes/src/gfxTextRunCache.cpp b/gfx/thebes/src/gfxTextRunCache.cpp index 6744695b981..77282d02842 100644 --- a/gfx/thebes/src/gfxTextRunCache.cpp +++ b/gfx/thebes/src/gfxTextRunCache.cpp @@ -37,302 +37,391 @@ #include "gfxTextRunCache.h" -static inline PRBool -IsAscii(const char *aString, PRUint32 aLength) +#include "nsExpirationTracker.h" + +static inline PRUint32 +HashMix(PRUint32 aHash, PRUnichar aCh) { - const char *end = aString + aLength; - while (aString < end) { - if (0x80 & *aString) - return PR_FALSE; - ++aString; - } - return PR_TRUE; + return (aHash >> 28) ^ (aHash << 4) ^ aCh; } -static inline PRBool -IsAscii(const PRUnichar *aString, PRUint32 aLength) -{ - const PRUnichar *end = aString + aLength; - while (aString < end) { - if (0x0080 <= *aString) - return PR_FALSE; - ++aString; - } - return PR_TRUE; -} - -static inline PRBool -Is8Bit(const PRUnichar *aString, PRUint32 aLength) -{ - const PRUnichar *end = aString + aLength; - while (aString < end) { - if (0x0100 <= *aString) - return PR_FALSE; - ++aString; - } - return PR_TRUE; -} - -gfxTextRunCache* gfxTextRunCache::mGlobalCache = nsnull; - -static int gDisableCache = -1; - -gfxTextRunCache::gfxTextRunCache() -{ - if (getenv("MOZ_GFX_NO_TEXT_CACHE")) - gDisableCache = 1; - else - gDisableCache = 0; - - mHashTableUTF16.Init(); - mHashTableASCII.Init(); - - mLastUTF16Eviction = mLastASCIIEviction = PR_Now(); -} - -// static -nsresult -gfxTextRunCache::Init() -{ - NS_ASSERTION(!mGlobalCache, "Why do we have an mGlobalCache?"); - mGlobalCache = new gfxTextRunCache(); - - if (!mGlobalCache) { - return NS_ERROR_OUT_OF_MEMORY; - } - - return NS_OK; -} - -// static -void -gfxTextRunCache::Shutdown() -{ - delete mGlobalCache; - mGlobalCache = nsnull; -} - static PRUint32 -ComputeFlags(PRBool aIsRTL, PRBool aEnableSpacing) +HashString(const PRUnichar *aText, PRUint32 aLength, PRUint32 *aFlags) { - PRUint32 flags = 0; - if (aIsRTL) { - flags |= gfxTextRunFactory::TEXT_IS_RTL; + *aFlags &= ~(gfxFontGroup::TEXT_HAS_SURROGATES | gfxFontGroup::TEXT_IS_ASCII); + PRUint32 i; + PRUint32 hashCode = 0; + PRUnichar allBits = 0; + for (i = 0; i < aLength; ++i) { + PRUnichar ch = aText[i]; + hashCode = HashMix(hashCode, ch); + allBits |= ch; + if (IS_SURROGATE(ch)) { + *aFlags |= gfxFontGroup::TEXT_HAS_SURROGATES; + } } - if (aEnableSpacing) { - flags |= gfxTextRunFactory::TEXT_ENABLE_SPACING | - gfxTextRunFactory::TEXT_ABSOLUTE_SPACING | - gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING; + if (!(allBits & ~0x7F)) { + *aFlags |= gfxFontGroup::TEXT_IS_ASCII; } - return flags; + return hashCode; } -gfxTextRun* -gfxTextRunCache::GetOrMakeTextRun(gfxContext *aContext, gfxFontGroup *aFontGroup, - const PRUnichar *aString, PRUint32 aLength, - PRUint32 aAppUnitsPerDevUnit, PRBool aIsRTL, - PRBool aEnableSpacing, PRBool *aCallerOwns) +static PRUint32 +HashString(const PRUint8 *aText, PRUint32 aLength, PRUint32 *aFlags) { - // Choose pessimistic flags since we don't want to bother analyzing the string - gfxTextRunFactory::Parameters params = { - aContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit - }; - PRUint32 flags = ComputeFlags(aIsRTL, aEnableSpacing); - if (IsAscii(aString, aLength)) { - flags |= gfxTextRunFactory::TEXT_IS_ASCII; - } else { - for (PRUint32 i = 0; i < aLength; ++i) { - if (NS_IS_HIGH_SURROGATE(aString[i])) { - flags |= gfxTextRunFactory::TEXT_HAS_SURROGATES; - break; - } - } + *aFlags &= ~(gfxFontGroup::TEXT_HAS_SURROGATES | gfxFontGroup::TEXT_IS_ASCII); + *aFlags |= gfxFontGroup::TEXT_IS_8BIT; + PRUint32 i; + PRUint32 hashCode = 0; + PRUint8 allBits = 0; + for (i = 0; i < aLength; ++i) { + PRUint8 ch = aText[i]; + hashCode = HashMix(hashCode, ch); + allBits |= ch; } - - gfxTextRun *tr = nsnull; - // Don't cache textruns that use spacing - if (!gDisableCache && !aEnableSpacing) { - // Evict first, to make sure that the textrun we return is live. - EvictUTF16(); - - TextRunEntry *entry; - nsDependentSubstring keyStr(aString, aString + aLength); - FontGroupAndString key(aFontGroup, &keyStr); - - if (mHashTableUTF16.Get(key, &entry)) { - gfxTextRun *cachedTR = entry->textRun; - // Check that this matches what we wanted. If it doesn't, we leave - // this cache entry alone and return a fresh, caller-owned textrun - // below. - if (cachedTR->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit && - cachedTR->IsRightToLeft() == aIsRTL) { - entry->Used(); - tr = cachedTR; - tr->SetContext(aContext); - } - } else { - key.Realize(); - // Text is persistent since it's in the key, which will live as - // long as this textrun. - tr = aFontGroup->MakeTextRun(key.GetRealString(), aLength, ¶ms, - flags | gfxTextRunFactory::TEXT_IS_PERSISTENT); - entry = new TextRunEntry(tr); - mHashTableUTF16.Put(key, entry); - } + if (!(allBits & ~0x7F)) { + *aFlags |= gfxFontGroup::TEXT_IS_ASCII; } - - if (tr) { - *aCallerOwns = PR_FALSE; - } else { - // Textrun is not in the cache for some reason. - *aCallerOwns = PR_TRUE; - PRUnichar *newStr = new PRUnichar[aLength]; - if (newStr) { - memcpy(newStr, aString, sizeof(PRUnichar)*aLength); - tr = aFontGroup->MakeTextRun(newStr, aLength, ¶ms, flags); - } - } - - return tr; + return hashCode; } -gfxTextRun* -gfxTextRunCache::GetOrMakeTextRun(gfxContext *aContext, gfxFontGroup *aFontGroup, - const char *aString, PRUint32 aLength, - PRUint32 aAppUnitsPerDevUnit, PRBool aIsRTL, - PRBool aEnableSpacing, PRBool *aCallerOwns) +static void *GetCacheKeyFontOrGroup(gfxTextRun *aTextRun) { - // Choose pessimistic flags since we don't want to bother analyzing the string - gfxTextRunFactory::Parameters params = { - aContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit - }; - PRUint32 flags = ComputeFlags(aIsRTL, aEnableSpacing) | gfxTextRunFactory::TEXT_IS_8BIT; - if (IsAscii(aString, aLength)) - flags |= gfxTextRunFactory::TEXT_IS_ASCII; + PRUint32 glyphRunCount; + const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&glyphRunCount); + gfxFontGroup *fontGroup = aTextRun->GetFontGroup(); + gfxFont *firstFont = fontGroup->GetFontAt(0); + return glyphRunCount == 1 && glyphRuns[0].mFont == firstFont + ? NS_STATIC_CAST(void *, firstFont) + : NS_STATIC_CAST(void *, fontGroup); +} - const PRUint8 *str = reinterpret_cast(aString); +static const PRUnichar * +CloneText(const PRUnichar *aText, PRUint32 aLength, + nsAutoArrayPtr *aBuffer, PRUint32 aFlags) +{ + if (*aBuffer == aText || (aFlags & gfxFontGroup::TEXT_IS_PERSISTENT)) + return aText; + PRUnichar *newText = new PRUnichar[aLength]; + if (!newText) + return nsnull; + memcpy(newText, aText, aLength*sizeof(PRUnichar)); + *aBuffer = newText; + return newText; +} - gfxTextRun *tr = nsnull; - // Don't cache textruns that use spacing - if (!gDisableCache && !aEnableSpacing) { - // Evict first, to make sure that the textrun we return is live. - EvictASCII(); - - TextRunEntry *entry; - nsDependentCSubstring keyStr(aString, aString + aLength); - FontGroupAndCString key(aFontGroup, &keyStr); +static const PRUint8 * +CloneText(const PRUint8 *aText, PRUint32 aLength, + nsAutoArrayPtr *aBuffer, PRUint32 aFlags) +{ + if (*aBuffer == aText || (aFlags & gfxFontGroup::TEXT_IS_PERSISTENT)) + return aText; + PRUint8 *newText = new PRUint8[aLength]; + if (!newText) + return nsnull; + memcpy(newText, aText, aLength); + *aBuffer = newText; + return newText; +} - if (mHashTableASCII.Get(key, &entry)) { - gfxTextRun *cachedTR = entry->textRun; - // Check that this matches what we wanted. If it doesn't, we leave - // this cache entry alone and return a fresh, caller-owned textrun - // below. - if (cachedTR->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit && - cachedTR->IsRightToLeft() == aIsRTL) { - entry->Used(); - tr = cachedTR; - tr->SetContext(aContext); - } - } else { - key.Realize(); - // Text is persistent since it's in the key, which will live as - // long as this textrun. - tr = aFontGroup->MakeTextRun(reinterpret_cast(key.GetRealString()), - aLength, ¶ms, - flags | gfxTextRunFactory::TEXT_IS_PERSISTENT); - entry = new TextRunEntry(tr); - mHashTableASCII.Put(key, entry); - } - } - - if (tr) { - *aCallerOwns = PR_FALSE; - } else { - // Textrun is not in the cache for some reason. +gfxTextRun * +gfxTextRunCache::GetOrMakeTextRun(const PRUnichar *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, + const gfxFontGroup::Parameters *aParams, + PRUint32 aFlags, PRBool *aCallerOwns) +{ + if (aCallerOwns) { *aCallerOwns = PR_TRUE; - PRUint8 *newStr = new PRUint8[aLength]; - if (newStr) { - memcpy(newStr, str, aLength); - tr = aFontGroup->MakeTextRun(newStr, aLength, ¶ms, flags); + } + if (aLength == 0) { + aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT; + } else if (aLength == 1 && aText[0] == ' ') { + aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT; + static const PRUnichar space = ' '; + aText = &space; + } + + PRUint32 hashCode = HashString(aText, aLength, &aFlags); + gfxFont *font = aFontGroup->GetFontAt(0); + CacheHashKey key(font, aText, aLength, aParams->mAppUnitsPerDevUnit, aFlags, hashCode); + CacheHashEntry *entry = nsnull; + if (font) { + entry = mCache.GetEntry(key); + } + if (!entry) { + key.mFontOrGroup = aFontGroup; + entry = mCache.GetEntry(key); + } + nsAutoArrayPtr text; + if (entry) { + gfxTextRun *textRun = entry->mTextRun; + if (aCallerOwns) { + *aCallerOwns = PR_FALSE; + return textRun; + } + aText = CloneText(aText, aLength, &text, aFlags); + if (!aText) + return nsnull; + gfxTextRun *newRun = + textRun->Clone(aParams, aText, aLength, aFontGroup, aFlags); + if (newRun) { + entry->mTextRun = newRun; + NotifyRemovedFromCache(textRun); + text.forget(); + return newRun; } } - return tr; + aText = CloneText(aText, aLength, &text, aFlags); + if (!aText) + return nsnull; + gfxTextRun *newRun = + aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags); + if (newRun) { + key.mFontOrGroup = GetCacheKeyFontOrGroup(newRun); + entry = mCache.PutEntry(key); + if (entry) { + entry->mTextRun = newRun; + } + NS_ASSERTION(!entry || entry == mCache.GetEntry(GetKeyForTextRun(newRun)), + "Inconsistent hashing"); + } + text.forget(); + return newRun; +} + +gfxTextRun * +gfxTextRunCache::GetOrMakeTextRun(const PRUint8 *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, + const gfxFontGroup::Parameters *aParams, + PRUint32 aFlags, PRBool *aCallerOwns) +{ + if (aCallerOwns) { + *aCallerOwns = PR_TRUE; + } + if (aLength == 0) { + aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT; + } else if (aLength == 1 && aText[0] == ' ') { + aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT; + static const PRUint8 space = ' '; + aText = &space; + } + + PRUint32 hashCode = HashString(aText, aLength, &aFlags); + gfxFont *font = aFontGroup->GetFontAt(0); + CacheHashKey key(font, aText, aLength, aParams->mAppUnitsPerDevUnit, aFlags, hashCode); + CacheHashEntry *entry = nsnull; + if (font) { + entry = mCache.GetEntry(key); + } + if (!entry) { + key.mFontOrGroup = aFontGroup; + entry = mCache.GetEntry(key); + } + nsAutoArrayPtr text; + if (entry) { + gfxTextRun *textRun = entry->mTextRun; + if (aCallerOwns) { + *aCallerOwns = PR_FALSE; + return textRun; + } + aText = CloneText(aText, aLength, &text, aFlags); + if (!aText) + return nsnull; + gfxTextRun *newRun = + textRun->Clone(aParams, aText, aLength, + aFontGroup, aFlags); + if (newRun) { + entry->mTextRun = newRun; + NotifyRemovedFromCache(textRun); + text.forget(); + return newRun; + } + } + + aText = CloneText(aText, aLength, &text, aFlags); + if (!aText) + return nsnull; + gfxTextRun *newRun = + aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags); + if (newRun) { + key.mFontOrGroup = GetCacheKeyFontOrGroup(newRun); + entry = mCache.PutEntry(key); + if (entry) { + entry->mTextRun = newRun; + } + NS_ASSERTION(!entry || entry == mCache.GetEntry(GetKeyForTextRun(newRun)), + "Inconsistent hashing"); + } + text.forget(); + return newRun; +} + +gfxTextRunCache::CacheHashKey +gfxTextRunCache::GetKeyForTextRun(gfxTextRun *aTextRun) +{ + PRUint32 hashCode; + const void *text; + PRUint32 length = aTextRun->GetLength(); + if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) { + PRUint32 flags; + text = aTextRun->GetText8Bit(); + hashCode = HashString(aTextRun->GetText8Bit(), length, &flags); + } else { + PRUint32 flags; + text = aTextRun->GetTextUnicode(); + hashCode = HashString(aTextRun->GetTextUnicode(), length, &flags); + } + void *fontOrGroup = GetCacheKeyFontOrGroup(aTextRun); + return CacheHashKey(fontOrGroup, text, length, aTextRun->GetAppUnitsPerDevUnit(), + aTextRun->GetFlags(), hashCode); +} + +void +gfxTextRunCache::RemoveTextRun(gfxTextRun *aTextRun) +{ + CacheHashKey key = GetKeyForTextRun(aTextRun); +#ifdef DEBUG + CacheHashEntry *entry = mCache.GetEntry(key); + NS_ASSERTION(entry && entry->mTextRun == aTextRun, + "Failed to find textrun in cache"); +#endif + mCache.RemoveEntry(key); +} + +static PRBool +CompareDifferentWidthStrings(const PRUint8 *aStr1, const PRUnichar *aStr2, + PRUint32 aLength) +{ + PRUint32 i; + for (i = 0; i < aLength; ++i) { + if (aStr1[i] != aStr2[i]) + return PR_FALSE; + } + return PR_TRUE; +} + +PRBool +gfxTextRunCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const +{ + gfxTextRun *textRun = mTextRun; + if (!textRun) + return PR_FALSE; + PRUint32 length = textRun->GetLength(); + if (aKey->mFontOrGroup != GetCacheKeyFontOrGroup(textRun) || + aKey->mLength != length || + aKey->mAppUnitsPerDevUnit != textRun->GetAppUnitsPerDevUnit() || + ((aKey->mFlags ^ textRun->GetFlags()) & FLAG_MASK)) + return PR_FALSE; + + if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) { + if (aKey->mFlags & gfxFontGroup::TEXT_IS_8BIT) + return memcmp(textRun->GetText8Bit(), aKey->mString, length) == 0; + return CompareDifferentWidthStrings(textRun->GetText8Bit(), + NS_STATIC_CAST(const PRUnichar *, aKey->mString), length); + } else { + if (!(aKey->mFlags & gfxFontGroup::TEXT_IS_8BIT)) + return memcmp(textRun->GetTextUnicode(), aKey->mString, length*sizeof(PRUnichar)) == 0; + return CompareDifferentWidthStrings(NS_STATIC_CAST(const PRUint8 *, aKey->mString), + textRun->GetTextUnicode(), length); + } +} + +PLDHashNumber +gfxTextRunCache::CacheHashEntry::HashKey(const KeyTypePointer aKey) +{ + return aKey->mStringHash + (long)aKey->mFontOrGroup + aKey->mAppUnitsPerDevUnit + + (aKey->mFlags & FLAG_MASK); } /* - * Stupid eviction algorithm: every 3*EVICT_AGE (3*1s), - * evict every run that hasn't been used for more than a second. - * - * Don't evict anything if we have less than EVICT_MIN_COUNT (1000) - * entries in the cache. - * - * XXX todo Don't use PR_Now(); use RDTSC or something for the timing. - * XXX todo Use a less-stupid eviction algorithm - * XXX todo Tweak EVICT_MIN_COUNT based on actual browsing + * Cache textruns and expire them after 3*10 seconds of no use */ +class TextRunCache : public nsExpirationTracker { +public: + enum { TIMEOUT_SECONDS = 10 }; + TextRunCache() + : nsExpirationTracker(TIMEOUT_SECONDS*1000) {} + ~TextRunCache() { + AgeAllGenerations(); + } -#define EVICT_MIN_COUNT 1000 + // This gets called when the timeout has expired on a gfxTextRun + virtual void NotifyExpired(gfxTextRun *aTextRun) { + RemoveObject(aTextRun); + mCache.RemoveTextRun(aTextRun); + delete aTextRun; + } -// 1s, in PRTime units (microseconds) -#define EVICT_AGE 1000000 + gfxTextRunCache mCache; +}; -void -gfxTextRunCache::EvictUTF16() +static TextRunCache *gTextRunCache = nsnull; + +static nsresult +UpdateOwnership(gfxTextRun *aTextRun, PRBool aOwned) { - PRTime evictBarrier = PR_Now(); + if (!aTextRun) + return nsnull; + if (aOwned) + return gTextRunCache->AddObject(aTextRun); + if (!aTextRun->GetExpirationState()->IsTracked()) + return NS_OK; + return gTextRunCache->MarkUsed(aTextRun); +} - if (mLastUTF16Eviction > (evictBarrier - (3*EVICT_AGE))) - return; +gfxTextRun * +gfxGlobalTextRunCache::GetTextRun(const PRUnichar *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, + gfxContext *aRefContext, + PRUint32 aAppUnitsPerDevUnit, + PRUint32 aFlags) +{ + if (!gTextRunCache) + return nsnull; + PRBool owned; + gfxTextRunFactory::Parameters params = { + aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit + }; + nsAutoPtr textRun; + textRun = gTextRunCache->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, ¶ms, aFlags, &owned); + nsresult rv = UpdateOwnership(textRun, owned); + if (NS_FAILED(rv)) + return nsnull; + return textRun.forget(); +} - if (mHashTableUTF16.Count() < EVICT_MIN_COUNT) - return; +gfxTextRun * +gfxGlobalTextRunCache::GetTextRun(const PRUint8 *aText, PRUint32 aLength, + gfxFontGroup *aFontGroup, + gfxContext *aRefContext, + PRUint32 aAppUnitsPerDevUnit, + PRUint32 aFlags) +{ + if (!gTextRunCache) + return nsnull; + PRBool owned; + gfxTextRunFactory::Parameters params = { + aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit + }; + nsAutoPtr textRun; + textRun = gTextRunCache->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, ¶ms, aFlags, &owned); + nsresult rv = UpdateOwnership(textRun, owned); + if (NS_FAILED(rv)) + return nsnull; + return textRun.forget(); +} - //fprintf (stderr, "Evicting UTF16\n"); - mLastUTF16Eviction = evictBarrier; - evictBarrier -= EVICT_AGE; - mHashTableUTF16.Enumerate(UTF16EvictEnumerator, &evictBarrier); +nsresult +gfxGlobalTextRunCache::Init() +{ + gTextRunCache = new TextRunCache(); + return gTextRunCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } void -gfxTextRunCache::EvictASCII() +gfxGlobalTextRunCache::Shutdown() { - PRTime evictBarrier = PR_Now(); - - if (mLastASCIIEviction > (evictBarrier - (3*EVICT_AGE))) - return; - - if (mHashTableASCII.Count() < EVICT_MIN_COUNT) - return; - - //fprintf (stderr, "Evicting ASCII\n"); - mLastASCIIEviction = evictBarrier; - evictBarrier -= EVICT_AGE; - mHashTableASCII.Enumerate(ASCIIEvictEnumerator, &evictBarrier); -} - -PLDHashOperator -gfxTextRunCache::UTF16EvictEnumerator(const FontGroupAndString& key, - nsAutoPtr &value, - void *closure) -{ - PRTime t = *(PRTime *)closure; - - if (value->lastUse < t) - return PL_DHASH_REMOVE; - - return PL_DHASH_NEXT; -} - -PLDHashOperator -gfxTextRunCache::ASCIIEvictEnumerator(const FontGroupAndCString& key, - nsAutoPtr &value, - void *closure) -{ - PRTime t = *(PRTime *)closure; - - if (value->lastUse < t) - return PL_DHASH_REMOVE; - - return PL_DHASH_NEXT; + delete gTextRunCache; + gTextRunCache = nsnull; }