Author: stippi Date: 2010-08-16 21:24:20 +0200 (Mon, 16 Aug 2010) New Revision: 38154 Changeset: http://dev.haiku-os.org/changeset/38154 Modified: haiku/trunk/src/servers/app/GlyphLayoutEngine.h Log: Rewrote the fake font overlay support, as the previous implementation had several problems: First of all it duplicated the FontCacheEntry retrieval and locking code. Second it was really slow to do the whole lookup for every single glyph. Third is used the FontManager without locking, which could result to app_server crashes, mostly at startup, while the FontManager thread was scanning the fonts and the glyph layout was already using it. Forth it dereferenced a FontStyle pointer without checking it against NULL. And lastly it didn't use the font size of the original font for the fallback font. The new algorithm addresses not only these, but also puts the glyphs which are unsupported by a font and retrieved from the alternative font, into the FontCacheEntry of the original font, so that the HasGlyphs() check succeeds the next time around and does not need to use the fallback algorithm anymore. There is also less manual management of locking and FontCacheEntry recycling, this is now taking care of by FontCacheReference objects on the stack. Modified: haiku/trunk/src/servers/app/GlyphLayoutEngine.h =================================================================== --- haiku/trunk/src/servers/app/GlyphLayoutEngine.h 2010-08-16 19:17:11 UTC (rev 38153) +++ haiku/trunk/src/servers/app/GlyphLayoutEngine.h 2010-08-16 19:24:20 UTC (rev 38154) @@ -19,44 +19,70 @@ #include <ctype.h> class FontCacheReference { - public: - FontCacheReference() - : fCacheEntry(NULL) - , fWriteLocked(false) - { - } - ~FontCacheReference() - { - if (fCacheEntry) { - if (fWriteLocked) - fCacheEntry->WriteUnlock(); - else - fCacheEntry->ReadUnlock(); - - FontCache::Default()->Recycle( - fCacheEntry); - } - } - void SetTo(FontCacheEntry* entry, - bool writeLocked) - { - fCacheEntry = entry; - fWriteLocked = writeLocked; - } - inline FontCacheEntry* Entry() const - { return fCacheEntry; } - inline bool WriteLocked() const - { return fWriteLocked; } +public: + FontCacheReference() + : + fCacheEntry(NULL), + fWriteLocked(false) + { + } - private: + ~FontCacheReference() + { + Unset(); + } + + void SetTo(FontCacheEntry* entry, bool writeLocked) + { + // NOTE: If the semantics are changed such + // that the reference to a previous entry + // is properly released, then don't forget + // to adapt existing which transfers + // responsibility of entries between + // references! + fCacheEntry = entry; + fWriteLocked = writeLocked; + } + + void Unset() + { + if (fCacheEntry == NULL) + return; + + if (fWriteLocked) + fCacheEntry->WriteUnlock(); + else + fCacheEntry->ReadUnlock(); + + FontCache::Default()->Recycle(fCacheEntry); + } + + inline FontCacheEntry* Entry() const + { + return fCacheEntry; + } + + inline bool WriteLocked() const + { + return fWriteLocked; + } + +private: FontCacheEntry* fCacheEntry; bool fWriteLocked; }; class GlyphLayoutEngine { - public: +public: + static bool IsWhiteSpace(uint32 glyphCode); + static FontCacheEntry* FontCacheEntryFor(const ServerFont& font, + const FontCacheEntry* disallowedEntry, + const char* utf8String, int32 length, + FontCacheReference& cacheReference, + bool needsWriteLock); + template<class GlyphConsumer> static bool LayoutGlyphs(GlyphConsumer& consumer, const ServerFont& font, @@ -68,15 +94,12 @@ const BPoint* offsets = NULL, FontCacheReference* cacheReference = NULL); - static bool IsWhiteSpace(uint32 glyphCode); - - private: +private: GlyphLayoutEngine(); virtual ~GlyphLayoutEngine(); }; -// IsWhiteSpace inline bool GlyphLayoutEngine::IsWhiteSpace(uint32 charCode) { @@ -97,42 +120,118 @@ } -// LayoutGlyphs +inline FontCacheEntry* +GlyphLayoutEngine::FontCacheEntryFor(const ServerFont& font, + const FontCacheEntry* disallowedEntry, const char* utf8String, int32 length, + FontCacheReference& cacheReference, bool needsWriteLock) +{ + ASSERT(cacheReference.Entry() == NULL); + + FontCache* cache = FontCache::Default(); + FontCacheEntry* entry = cache->FontCacheEntryFor(font); + if (entry == NULL) + return NULL; + + if (entry == disallowedEntry) { + cache->Recycle(entry); + return NULL; + } + + if (needsWriteLock) { + if (!entry->WriteLock()) { + cache->Recycle(entry); + return NULL; + } + } else { + if (!entry->ReadLock()) { + cache->Recycle(entry); + return NULL; + } + } + + // At this point, we have a valid FontCacheEntry and it is locked in the + // proper mode. We can setup the FontCacheReference so it takes care of + // the locking and recycling from now and return the entry. + cacheReference.SetTo(entry, needsWriteLock); + return entry; +} + + template<class GlyphConsumer> inline bool GlyphLayoutEngine::LayoutGlyphs(GlyphConsumer& consumer, const ServerFont& font, const char* utf8String, int32 length, const escapement_delta* delta, bool kerning, uint8 spacing, - const BPoint* offsets, FontCacheReference* cacheReference) + const BPoint* offsets, FontCacheReference* _cacheReference) { // TODO: implement spacing modes - FontCacheEntry* entry = NULL; + FontCacheReference cacheReference; FontCacheEntry* fallbackEntry = NULL; - bool needsWriteLock = false; - if (cacheReference) { - entry = cacheReference->Entry(); - needsWriteLock = cacheReference->WriteLocked(); + FontCacheReference fallbackCacheReference; + if (_cacheReference != NULL) { + entry = _cacheReference->Entry(); + // When there is already a cacheReference, it means there was already + // an iteration over the glyphs. The use-case is for example to do + // a layout pass to get the string width for the bounding box, then a + // second layout pass to actually render the glyphs to the screen. + // This means that the fallback entry mechanism will not do any good + // for the second pass, since the fallback glyphs have been stored in + // the original entry. } - if (!entry) { - FontCache* cache = FontCache::Default(); - entry = cache->FontCacheEntryFor(font); - - if (!entry || !entry->ReadLock()) { - cache->Recycle(entry); + if (entry == NULL) { + entry = FontCacheEntryFor(font, NULL, utf8String, length, + cacheReference, false); + + if (entry == NULL) return false; - } - - needsWriteLock = !entry->HasGlyphs(utf8String, length); - + + // See if the entry already has the glyphs cached. Switch to a write + // lock if not. + bool needsWriteLock = !entry->HasGlyphs(utf8String, length); if (needsWriteLock) { + // This also means we need the fallback font, since potentially, + // we have to obtain missing glyphs from it. We need to obtain + // the fallback font while we have not locked anything, since + // locking the FontManager with the write-lock held can obvisouly + // lead to a deadlock. + + cacheReference.SetTo(NULL, false); entry->ReadUnlock(); + + if (gFontManager->Lock()) { + // TODO: We always get the fallback glyphs from VL Gothic at the + // moment, but of course the fallback font should a) contain the + // missing glyphs at all and b) be similar to the original font. + // So there should be a mapping of some kind to know the most + // suitable fallback font. + FontStyle* fallbackStyle = gFontManager->GetStyleByIndex( + "VL Gothic", 0); + if (fallbackStyle != NULL) { + ServerFont fallbackFont(*fallbackStyle, font.Size()); + gFontManager->Unlock(); + // Force the write-lock on the fallback entry, since we + // don't transfer or copy GlyphCache objects from one cache + // to the other, but create new glyphs which are stored in + // "entry" in any case, which requires the write cache for + // sure (used FontEngine of fallbackEntry). + fallbackEntry = FontCacheEntryFor(fallbackFont, entry, + utf8String, length, fallbackCacheReference, true); + // NOTE: We don't care if fallbackEntry is NULL, fetching + // alternate glyphs will simply not work. + } else + gFontManager->Unlock(); + } + if (!entry->WriteLock()) { - cache->Recycle(entry); - return false; + FontCache::Default()->Recycle(entry); + return NULL; } + + // Update the FontCacheReference, since the locking kind changed. + cacheReference.SetTo(entry, needsWriteLock); } } // else the entry was already used and is still locked @@ -172,68 +271,18 @@ x += IsWhiteSpace(charCode) ? delta->space : delta->nonspace; } - const GlyphCache* glyph = entry->Glyph(charCode); + const GlyphCache* glyph = entry->Glyph(charCode, fallbackEntry); if (glyph == NULL) { - // Try to find a suitable glyph in another font - FontCache* cache = FontCache::Default(); - bool needsWriteLock = false; - ServerFont fallbackFont(*(gFontManager->GetStyleByIndex("VL Gothic", - 0))); - // We always try to get the glyph from VL Gothic, so we can - // display japanese characters. Other scripts (indian, ...) - // should be handled too, perhaps with a charcode > font - // mapping. - // TODO : the font should not be hardcoded, but somehow derived - // from the one that missed a glyph. - bool consumed = true; - fallbackEntry = cache->FontCacheEntryFor(fallbackFont); - if (fallbackEntry != entry) - { - if (!fallbackEntry || !fallbackEntry->ReadLock()) { - cache->Recycle(fallbackEntry); - continue; - } - - needsWriteLock = !fallbackEntry->HasGlyphs(utf8String, length); - - if (needsWriteLock) { - fallbackEntry->ReadUnlock(); - if (!fallbackEntry->WriteLock()) { - cache->Recycle(fallbackEntry); - continue; - } - } - - glyph = fallbackEntry->Glyph(charCode); - if (glyph != NULL && !consumer.ConsumeGlyph(index, charCode, - glyph, fallbackEntry, x, y)) { - advanceX = 0; - advanceY = 0; - consumed = false; - } - - if (needsWriteLock) - fallbackEntry->WriteUnlock(); - else - fallbackEntry->ReadUnlock(); + consumer.ConsumeEmptyGlyph(index, charCode, x, y); + continue; + } else { + if (!consumer.ConsumeGlyph(index, charCode, glyph, entry, x, y)) { + advanceX = 0; + advanceY = 0; + break; } - FontCache::Default()->Recycle(fallbackEntry); - - if (glyph == NULL) { - consumer.ConsumeEmptyGlyph(index, charCode, x, y); - continue; - } - - if (!consumed) - break; } - if (!consumer.ConsumeGlyph(index, charCode, glyph, entry, x, y)) { - advanceX = 0; - advanceY = 0; - break; - } - // get next increment for pen position advanceX = glyph->advance_x; advanceY = glyph->advance_y; @@ -248,17 +297,15 @@ y += advanceY; consumer.Finish(x, y); - if (!cacheReference) { - if (needsWriteLock) - entry->WriteUnlock(); - else - entry->ReadUnlock(); - - FontCache::Default()->Recycle(entry); - } else { - // the reference will take care of locking - if (!cacheReference->Entry()) - cacheReference->SetTo(entry, needsWriteLock); + if (_cacheReference != NULL && _cacheReference->Entry() == NULL) { + // The caller passed a FontCacheReference, but this is the first + // iteration -> switch the ownership from the stack allocated + // FontCacheReference to the one passed by the caller. The fallback + // FontCacheReference is not affected by this. Eventually, the fallback + // glyphs shall be cached in the original FontCacheEntry, so that it is + // not even used in this situation. + _cacheReference->SetTo(entry, cacheReference.WriteLocked()); + cacheReference.SetTo(NULL, false); } return true; }