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 "nsILanguageAtomService.h"
#include "nsIMemoryReporter.h"
#include "nsITimer.h"
#include "gfxFont.h"
#include "gfxPlatform.h"
@ -981,6 +982,36 @@ gfxFontCache::Shutdown()
#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
gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const
{
@ -1055,6 +1086,22 @@ gfxFontCache::DestroyFont(gfxFont *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
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 *
gfxFont::GetFontTable(PRUint32 aTag) {
hb_blob_t *blob;
@ -1767,40 +1828,43 @@ gfxFont::GetShapedWord(gfxContext *aContext,
aFlags);
CacheHashEntry *entry = mWordCache.PutEntry(key);
if (entry->mShapedWord) {
return entry->mShapedWord;
gfxShapedWord *sw = entry->mShapedWord;
if (sw) {
sw->ResetAge();
return sw;
}
entry->mShapedWord = gfxShapedWord::Create(aText, aLength,
aRunScript,
aAppUnitsPerDevUnit,
aFlags);
if (!entry->mShapedWord) {
NS_WARNING("failed to create gfxShapedWord - expect missing text");
sw = entry->mShapedWord = gfxShapedWord::Create(aText, aLength,
aRunScript,
aAppUnitsPerDevUnit,
aFlags);
NS_ASSERTION(sw != nsnull,
"failed to create gfxShapedWord - expect missing text");
if (!sw) {
return nsnull;
}
bool ok;
if (sizeof(T) == sizeof(PRUnichar)) {
ok = ShapeWord(aContext, entry->mShapedWord, (const PRUnichar*)aText);
ok = ShapeWord(aContext, sw, (const PRUnichar*)aText);
} else {
nsAutoString 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");
for (PRUint32 i = 0; i < aLength; ++i) {
if (aText[i] == ' ') {
entry->mShapedWord->SetIsSpace(i);
sw->SetIsSpace(i);
} else if (i > 0 &&
NS_IS_HIGH_SURROGATE(aText[i - 1]) &&
NS_IS_LOW_SURROGATE(aText[i])) {
entry->mShapedWord->SetIsLowSurrogate(i);
sw->SetIsLowSurrogate(i);
}
}
return entry->mShapedWord;
return sw;
}
bool

View File

@ -671,22 +671,27 @@ struct gfxTextRange {
* We're using 3 generations with a ten-second generation interval, so
* zero-refcount fonts will be deleted 20-30 seconds after their refcount
* 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> {
public:
enum { TIMEOUT_SECONDS = 10 };
gfxFontCache()
: nsExpirationTracker<gfxFont,3>(TIMEOUT_SECONDS*1000) { mFonts.Init(); }
~gfxFontCache() {
// 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.
}
enum {
FONT_TIMEOUT_SECONDS = 10,
SHAPED_WORD_TIMEOUT_SECONDS = 60
};
gfxFontCache();
~gfxFontCache();
/*
* 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
// if there's nothing matching in the cache
already_AddRefed<gfxFont> Lookup(const gfxFontEntry *aFontEntry,
const gfxFontStyle *aFontGroup);
const gfxFontStyle *aStyle);
// We created a new font (presumably because Lookup returned null);
// 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
@ -761,6 +766,10 @@ protected:
};
nsTHashtable<HashEntry> mFonts;
static PLDHashOperator AgeCachedWordsForFont(HashEntry* aHashEntry, void*);
static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache);
nsCOMPtr<nsITimer> mWordCacheExpirationTimer;
};
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:
// Call the appropriate shaper to generate glyphs for aText and store
// them into aShapedWord.
@ -1472,6 +1489,9 @@ protected:
nsTHashtable<CacheHashEntry> mWordCache;
static PLDHashOperator AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData);
static const PRUint32 kShapedWordCacheMaxAge = 3;
bool mIsValid;
// use synthetic bolding for environments where this is not supported
@ -1891,6 +1911,13 @@ public:
return mAppUnitsPerDevUnit;
}
void ResetAge() {
mAgeCounter = 0;
}
PRUint32 IncrementAge() {
return ++mAgeCounter;
}
void SetSimpleGlyph(PRUint32 aCharIndex, CompressedGlyph aGlyph) {
NS_ASSERTION(aGlyph.IsSimpleGlyph(), "Should be a simple glyph here");
if (mCharacterGlyphs) {
@ -1954,6 +1981,7 @@ private:
, mFlags(aFlags | gfxTextRunFactory::TEXT_IS_8BIT)
, mAppUnitsPerDevUnit(aAppUnitsPerDevUnit)
, mScript(aRunScript)
, mAgeCounter(0)
{
memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph));
PRUint8 *text = reinterpret_cast<PRUint8*>(&mCharacterGlyphs[aLength]);
@ -1967,6 +1995,7 @@ private:
, mFlags(aFlags)
, mAppUnitsPerDevUnit(aAppUnitsPerDevUnit)
, mScript(aRunScript)
, mAgeCounter(0)
{
memset(mCharacterGlyphs, 0, aLength * sizeof(CompressedGlyph));
PRUnichar *text = reinterpret_cast<PRUnichar*>(&mCharacterGlyphs[aLength]);
@ -2111,6 +2140,8 @@ private:
PRInt32 mAppUnitsPerDevUnit;
PRInt32 mScript;
PRUint32 mAgeCounter;
// The mCharacterGlyphs array is actually a variable-size member;
// when the ShapedWord is created, its size will be increased as necessary
// to allow the proper number of glyphs to be stored.