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

This commit is contained in:
roc+@cs.cmu.edu 2007-05-09 13:33:16 -07:00
Родитель ea66fe68a8
Коммит a38734a09b
4 изменённых файлов: 509 добавлений и 402 удалений

Просмотреть файл

@ -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;

Просмотреть файл

@ -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<class GenericString, class RealString>
struct FontGroupAndStringT {
FontGroupAndStringT(gfxFontGroup *fg, const GenericString* str)
: mFontGroup(fg), mString(str) { }
typedef typename RealString::char_type char_type;
FontGroupAndStringT(const FontGroupAndStringT<GenericString,RealString>& 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<gfxFontGroup> 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<class T>
struct FontGroupAndStringHashKeyT : public PLDHashEntryHdr {
typedef const T& KeyType;
typedef const T* KeyTypePointer;
FontGroupAndStringHashKeyT(KeyTypePointer aObj) : mObj(*aObj) { }
FontGroupAndStringHashKeyT(const FontGroupAndStringHashKeyT<T>& 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<nsAString, nsString> FontGroupAndString;
typedef FontGroupAndStringT<nsACString, nsCString> FontGroupAndCString;
typedef FontGroupAndStringHashKeyT<FontGroupAndString> FontGroupAndStringHashKey;
typedef FontGroupAndStringHashKeyT<FontGroupAndCString> FontGroupAndCStringHashKey;
nsClassHashtable<FontGroupAndStringHashKey, TextRunEntry> mHashTableUTF16;
nsClassHashtable<FontGroupAndCStringHashKey, TextRunEntry> mHashTableASCII;
void EvictUTF16();
void EvictASCII();
PRTime mLastUTF16Eviction;
PRTime mLastASCIIEviction;
static PLDHashOperator UTF16EvictEnumerator(const FontGroupAndString& key,
nsAutoPtr<TextRunEntry> &value,
void *closure);
static PLDHashOperator ASCIIEvictEnumerator(const FontGroupAndCString& key,
nsAutoPtr<TextRunEntry> &value,
void *closure);
nsTHashtable<CacheHashEntry> 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 */

Просмотреть файл

@ -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();

Просмотреть файл

@ -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, &params,
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, &params, 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<const PRUint8*>(aString);
static const PRUnichar *
CloneText(const PRUnichar *aText, PRUint32 aLength,
nsAutoArrayPtr<PRUnichar> *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<PRUint8> *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<const PRUint8 *>(key.GetRealString()),
aLength, &params,
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, &params, 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<PRUnichar> 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<PRUint8> 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<gfxTextRun,3> {
public:
enum { TIMEOUT_SECONDS = 10 };
TextRunCache()
: nsExpirationTracker<gfxTextRun,3>(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<gfxTextRun> textRun;
textRun = gTextRunCache->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, &params, 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<gfxTextRun> textRun;
textRun = gTextRunCache->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, &params, 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<TextRunEntry> &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<TextRunEntry> &value,
void *closure)
{
PRTime t = *(PRTime *)closure;
if (value->lastUse < t)
return PL_DHASH_REMOVE;
return PL_DHASH_NEXT;
delete gTextRunCache;
gTextRunCache = nsnull;
}