/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "GUIFontTTF.h" #include "windowing/GraphicContext.h" #include #include using namespace std::chrono_literals; namespace { constexpr auto FONT_CACHE_TIME_LIMIT = 1000ms; } template class CGUIFontCacheImpl { struct EntryList { using HashMap = std::multimap>>; using HashIter = typename HashMap::iterator; using AgeMap = std::multimap; ~EntryList() { Flush(); } HashIter Insert(size_t hash, std::unique_ptr> v) { auto r(hashMap.insert(typename HashMap::value_type(hash, std::move(v)))); if (r->second) ageMap.insert(typename AgeMap::value_type(r->second->m_lastUsed, r)); return r; } void Flush() { ageMap.clear(); hashMap.clear(); } typename HashMap::iterator FindKey(CGUIFontCacheKey key) { CGUIFontCacheHash hashGen; CGUIFontCacheKeysMatch keyMatch; auto range = hashMap.equal_range(hashGen(key)); for (auto ret = range.first; ret != range.second; ++ret) { if (keyMatch(ret->second->m_key, key)) { return ret; } } return hashMap.end(); } void UpdateAge(HashIter it, std::chrono::steady_clock::time_point now) { auto range = ageMap.equal_range(it->second->m_lastUsed); for (auto ageit = range.first; ageit != range.second; ++ageit) { if (ageit->second == it) { ageMap.erase(ageit); ageMap.insert(typename AgeMap::value_type(now, it)); it->second->m_lastUsed = now; return; } } } HashMap hashMap; AgeMap ageMap; }; EntryList m_list; CGUIFontCache* m_parent; public: explicit CGUIFontCacheImpl(CGUIFontCache* parent) : m_parent(parent) {} Value& Lookup(const CGraphicContext& context, Position& pos, const std::vector& colors, const vecText& text, uint32_t alignment, float maxPixelWidth, bool scrolling, std::chrono::steady_clock::time_point now, bool& dirtyCache); void Flush(); }; template CGUIFontCacheEntry::~CGUIFontCacheEntry() { delete &m_key.m_colors; delete &m_key.m_text; m_value.clear(); } template void CGUIFontCacheEntry::Assign(const CGUIFontCacheKey& key, std::chrono::steady_clock::time_point now) { m_key.m_pos = key.m_pos; m_key.m_colors.assign(key.m_colors.begin(), key.m_colors.end()); m_key.m_text.assign(key.m_text.begin(), key.m_text.end()); m_key.m_alignment = key.m_alignment; m_key.m_maxPixelWidth = key.m_maxPixelWidth; m_key.m_scrolling = key.m_scrolling; m_matrix = key.m_matrix; m_key.m_scaleX = key.m_scaleX; m_key.m_scaleY = key.m_scaleY; m_lastUsed = now; m_value.clear(); } template CGUIFontCache::CGUIFontCache(CGUIFontTTF& font) : m_impl(std::make_unique>(this)), m_font(font) { } template CGUIFontCache::~CGUIFontCache() = default; template Value& CGUIFontCache::Lookup(const CGraphicContext& context, Position& pos, const std::vector& colors, const vecText& text, uint32_t alignment, float maxPixelWidth, bool scrolling, std::chrono::steady_clock::time_point now, bool& dirtyCache) { if (!m_impl) m_impl = std::make_unique>(this); return m_impl->Lookup(context, pos, colors, text, alignment, maxPixelWidth, scrolling, now, dirtyCache); } template Value& CGUIFontCacheImpl::Lookup(const CGraphicContext& context, Position& pos, const std::vector& colors, const vecText& text, uint32_t alignment, float maxPixelWidth, bool scrolling, std::chrono::steady_clock::time_point now, bool& dirtyCache) { const CGUIFontCacheKey key(pos, const_cast&>(colors), const_cast(text), alignment, maxPixelWidth, scrolling, context.GetGUIMatrix(), context.GetGUIScaleX(), context.GetGUIScaleY()); auto i = m_list.FindKey(key); if (i == m_list.hashMap.end()) { // Cache miss dirtyCache = true; std::unique_ptr> entry; if (!m_list.ageMap.empty()) { const auto duration = std::chrono::duration_cast(now - m_list.ageMap.begin()->first); if (duration > FONT_CACHE_TIME_LIMIT) { entry = std::move(m_list.ageMap.begin()->second->second); m_list.hashMap.erase(m_list.ageMap.begin()->second); m_list.ageMap.erase(m_list.ageMap.begin()); } } // add new entry CGUIFontCacheHash hashgen; if (!entry) entry = std::make_unique>(*m_parent, key, now); else entry->Assign(key, now); return m_list.Insert(hashgen(key), std::move(entry))->second->m_value; } else { // Cache hit // Update the translation arguments so that they hold the offset to apply // to the cached values (but only in the dynamic case) pos.UpdateWithOffsets(i->second->m_key.m_pos, scrolling); // Update time in entry and move to the back of the list m_list.UpdateAge(i, now); dirtyCache = false; return i->second->m_value; } } template void CGUIFontCache::Flush() { m_impl->Flush(); } template void CGUIFontCacheImpl::Flush() { m_list.Flush(); } template CGUIFontCache::CGUIFontCache( CGUIFontTTF& font); template CGUIFontCache::~CGUIFontCache(); template CGUIFontCacheEntry::~CGUIFontCacheEntry(); template CGUIFontCacheStaticValue& CGUIFontCache< CGUIFontCacheStaticPosition, CGUIFontCacheStaticValue>::Lookup(const CGraphicContext& context, CGUIFontCacheStaticPosition&, const std::vector&, const vecText&, uint32_t, float, bool, std::chrono::steady_clock::time_point, bool&); template void CGUIFontCache::Flush(); template CGUIFontCache::CGUIFontCache( CGUIFontTTF& font); template CGUIFontCache::~CGUIFontCache(); template CGUIFontCacheEntry::~CGUIFontCacheEntry(); template CGUIFontCacheDynamicValue& CGUIFontCache< CGUIFontCacheDynamicPosition, CGUIFontCacheDynamicValue>::Lookup(const CGraphicContext& context, CGUIFontCacheDynamicPosition&, const std::vector&, const vecText&, uint32_t, float, bool, std::chrono::steady_clock::time_point, bool&); template void CGUIFontCache::Flush(); void CVertexBuffer::clear() { if (m_font) m_font->DestroyVertexBuffer(*this); }