bug 703100 - pt 4 - add timed expiration of cached gfxShapedWord records. r=roc

This commit is contained in:
Jonathan Kew 2012-01-05 11:54:45 +00:00
parent d5a2294830
commit 39b0dea87f
2 changed files with 122 additions and 27 deletions

View File

@ -48,6 +48,7 @@
#include "nsExpirationTracker.h" #include "nsExpirationTracker.h"
#include "nsILanguageAtomService.h" #include "nsILanguageAtomService.h"
#include "nsIMemoryReporter.h" #include "nsIMemoryReporter.h"
#include "nsITimer.h"
#include "gfxFont.h" #include "gfxFont.h"
#include "gfxPlatform.h" #include "gfxPlatform.h"
@ -981,6 +982,36 @@ gfxFontCache::Shutdown()
#endif #endif
} }
gfxFontCache::gfxFontCache()
: nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000)
{
mFonts.Init();
mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
if (mWordCacheExpirationTimer) {
mWordCacheExpirationTimer->
InitWithFuncCallback(WordCacheExpirationTimerCallback, this,
SHAPED_WORD_TIMEOUT_SECONDS * 1000,
nsITimer::TYPE_REPEATING_SLACK);
}
}
gfxFontCache::~gfxFontCache()
{
if (mWordCacheExpirationTimer) {
mWordCacheExpirationTimer->Cancel();
mWordCacheExpirationTimer = nsnull;
}
// Expire everything that has a zero refcount, so we don't leak them.
AgeAllGenerations();
// All fonts should be gone.
NS_WARN_IF_FALSE(mFonts.Count() == 0,
"Fonts still alive while shutting down gfxFontCache");
// Note that we have to delete everything through the expiration
// tracker, since there might be fonts not in the hashtable but in
// the tracker.
}
bool bool
gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const
{ {
@ -1055,6 +1086,22 @@ gfxFontCache::DestroyFont(gfxFont *aFont)
delete aFont; delete aFont;
} }
/*static*/
PLDHashOperator
gfxFontCache::AgeCachedWordsForFont(HashEntry* aHashEntry, void* aUserData)
{
aHashEntry->mFont->AgeCachedWords();
return PL_DHASH_NEXT;
}
/*static*/
void
gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache)
{
gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
cache->mFonts.EnumerateEntries(AgeCachedWordsForFont, nsnull);
}
void void
gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft) gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft)
{ {
@ -1096,6 +1143,20 @@ gfxFont::~gfxFont()
} }
} }
/*static*/
PLDHashOperator
gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData)
{
if (!aEntry->mShapedWord) {
NS_ASSERTION(aEntry->mShapedWord, "cache entry has no gfxShapedWord!");
return PL_DHASH_REMOVE;
}
if (aEntry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) {
return PL_DHASH_REMOVE;
}
return PL_DHASH_NEXT;
}
hb_blob_t * hb_blob_t *
gfxFont::GetFontTable(PRUint32 aTag) { gfxFont::GetFontTable(PRUint32 aTag) {
hb_blob_t *blob; hb_blob_t *blob;
@ -1767,40 +1828,43 @@ gfxFont::GetShapedWord(gfxContext *aContext,
aFlags); aFlags);
CacheHashEntry *entry = mWordCache.PutEntry(key); CacheHashEntry *entry = mWordCache.PutEntry(key);
if (entry->mShapedWord) { gfxShapedWord *sw = entry->mShapedWord;
return entry->mShapedWord; if (sw) {
sw->ResetAge();
return sw;
} }
entry->mShapedWord = gfxShapedWord::Create(aText, aLength, sw = entry->mShapedWord = gfxShapedWord::Create(aText, aLength,
aRunScript, aRunScript,
aAppUnitsPerDevUnit, aAppUnitsPerDevUnit,
aFlags); aFlags);
if (!entry->mShapedWord) { NS_ASSERTION(sw != nsnull,
NS_WARNING("failed to create gfxShapedWord - expect missing text"); "failed to create gfxShapedWord - expect missing text");
if (!sw) {
return nsnull; return nsnull;
} }
bool ok; bool ok;
if (sizeof(T) == sizeof(PRUnichar)) { if (sizeof(T) == sizeof(PRUnichar)) {
ok = ShapeWord(aContext, entry->mShapedWord, (const PRUnichar*)aText); ok = ShapeWord(aContext, sw, (const PRUnichar*)aText);
} else { } else {
nsAutoString utf16; nsAutoString utf16;
AppendASCIItoUTF16((const char*)aText, utf16); AppendASCIItoUTF16((const char*)aText, utf16);
ok = ShapeWord(aContext, entry->mShapedWord, utf16.BeginReading()); ok = ShapeWord(aContext, sw, utf16.BeginReading());
} }
NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text"); NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text");
for (PRUint32 i = 0; i < aLength; ++i) { for (PRUint32 i = 0; i < aLength; ++i) {
if (aText[i] == ' ') { if (aText[i] == ' ') {
entry->mShapedWord->SetIsSpace(i); sw->SetIsSpace(i);
} else if (i > 0 && } else if (i > 0 &&
NS_IS_HIGH_SURROGATE(aText[i - 1]) && NS_IS_HIGH_SURROGATE(aText[i - 1]) &&
NS_IS_LOW_SURROGATE(aText[i])) { NS_IS_LOW_SURROGATE(aText[i])) {
entry->mShapedWord->SetIsLowSurrogate(i); sw->SetIsLowSurrogate(i);
} }
} }
return entry->mShapedWord; return sw;
} }
bool bool

View File

@ -671,22 +671,27 @@ struct gfxTextRange {
* We're using 3 generations with a ten-second generation interval, so * We're using 3 generations with a ten-second generation interval, so
* zero-refcount fonts will be deleted 20-30 seconds after their refcount * zero-refcount fonts will be deleted 20-30 seconds after their refcount
* goes to zero, if timer events fire in a timely manner. * goes to zero, if timer events fire in a timely manner.
*
* The font cache also handles timed expiration of cached ShapedWords
* for "persistent" fonts: it has a repeating timer, and notifies
* each cached font to "age" its shaped words. The words will be released
* by the fonts if they get aged three times without being re-used in the
* meantime.
*
* Note that the ShapedWord timeout is much larger than the font timeout,
* so that in the case of a short-lived font, we'll discard the gfxFont
* completely, with all its words, and avoid the cost of aging the words
* individually. That only happens with longer-lived fonts.
*/ */
class THEBES_API gfxFontCache : public nsExpirationTracker<gfxFont,3> { class THEBES_API gfxFontCache : public nsExpirationTracker<gfxFont,3> {
public: public:
enum { TIMEOUT_SECONDS = 10 }; enum {
gfxFontCache() FONT_TIMEOUT_SECONDS = 10,
: nsExpirationTracker<gfxFont,3>(TIMEOUT_SECONDS*1000) { mFonts.Init(); } SHAPED_WORD_TIMEOUT_SECONDS = 60
~gfxFontCache() { };
// Expire everything that has a zero refcount, so we don't leak them.
AgeAllGenerations(); gfxFontCache();
// All fonts should be gone. ~gfxFontCache();
NS_WARN_IF_FALSE(mFonts.Count() == 0,
"Fonts still alive while shutting down gfxFontCache");
// Note that we have to delete everything through the expiration
// tracker, since there might be fonts not in the hashtable but in
// the tracker.
}
/* /*
* Get the global gfxFontCache. You must call Init() before * Get the global gfxFontCache. You must call Init() before
@ -703,7 +708,7 @@ public:
// Look up a font in the cache. Returns an addrefed pointer, or null // Look up a font in the cache. Returns an addrefed pointer, or null
// if there's nothing matching in the cache // if there's nothing matching in the cache
already_AddRefed<gfxFont> Lookup(const gfxFontEntry *aFontEntry, already_AddRefed<gfxFont> Lookup(const gfxFontEntry *aFontEntry,
const gfxFontStyle *aFontGroup); const gfxFontStyle *aStyle);
// We created a new font (presumably because Lookup returned null); // We created a new font (presumably because Lookup returned null);
// put it in the cache. The font's refcount should be nonzero. It is // put it in the cache. The font's refcount should be nonzero. It is
// allowable to add a new font even if there is one already in the // allowable to add a new font even if there is one already in the
@ -761,6 +766,10 @@ protected:
}; };
nsTHashtable<HashEntry> mFonts; nsTHashtable<HashEntry> mFonts;
static PLDHashOperator AgeCachedWordsForFont(HashEntry* aHashEntry, void*);
static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache);
nsCOMPtr<nsITimer> mWordCacheExpirationTimer;
}; };
class THEBES_API gfxTextRunFactory { class THEBES_API gfxTextRunFactory {
@ -1386,6 +1395,14 @@ public:
} }
} }
// Called by the gfxFontCache timer to increment the age of all the words,
// so that they'll expire after a sufficient period of non-use
void AgeCachedWords() {
if (mWordCache.IsInitialized()) {
(void)mWordCache.EnumerateEntries(AgeCacheEntry, this);
}
}
protected: protected:
// Call the appropriate shaper to generate glyphs for aText and store // Call the appropriate shaper to generate glyphs for aText and store
// them into aShapedWord. // them into aShapedWord.
@ -1472,6 +1489,9 @@ protected:
nsTHashtable<CacheHashEntry> mWordCache; nsTHashtable<CacheHashEntry> mWordCache;
static PLDHashOperator AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData);
static const PRUint32 kShapedWordCacheMaxAge = 3;
bool mIsValid; bool mIsValid;
// use synthetic bolding for environments where this is not supported // use synthetic bolding for environments where this is not supported
@ -1891,6 +1911,13 @@ public:
return mAppUnitsPerDevUnit; return mAppUnitsPerDevUnit;
} }
void ResetAge() {
mAgeCounter = 0;
}
PRUint32 IncrementAge() {
return ++mAgeCounter;
}
void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) { void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here"); NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here");
if (mCharacterGlyphs) { if (mCharacterGlyphs) {
@ -1954,6 +1981,7 @@ private:
, mFlags(aFlags | gfxTextRunFactory::TEXT_IS_8BIT) , mFlags(aFlags | gfxTextRunFactory::TEXT_IS_8BIT)
, mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit)
, mScript(aRunScript) , mScript(aRunScript)
, mAgeCounter(0)
{ {
memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph)); memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph));
PRUint8 *text = reinterpret_cast<PRUint8*>(&mCharacterGlyphs[aLength]); PRUint8 *text = reinterpret_cast<PRUint8*>(&mCharacterGlyphs[aLength]);
@ -1967,6 +1995,7 @@ private:
, mFlags(aFlags) , mFlags(aFlags)
, mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) , mAppUnitsPerDevUnit(aAppUnitsPerDevUnit)
, mScript(aRunScript) , mScript(aRunScript)
, mAgeCounter(0)
{ {
memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph)); memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph));
PRUnichar *text = reinterpret_cast<PRUnichar*>(&mCharacterGlyphs[aLength]); PRUnichar *text = reinterpret_cast<PRUnichar*>(&mCharacterGlyphs[aLength]);
@ -2111,6 +2140,8 @@ private:
PRInt32 mAppUnitsPerDevUnit; PRInt32 mAppUnitsPerDevUnit;
PRInt32 mScript; PRInt32 mScript;
PRUint32 mAgeCounter;
// The mCharacterGlyphs array is actually a variable-size member; // The mCharacterGlyphs array is actually a variable-size member;
// when the ShapedWord is created, its size will be increased as necessary // when the ShapedWord is created, its size will be increased as necessary
// to allow the proper number of glyphs to be stored. // to allow the proper number of glyphs to be stored.