/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include "SlsBitmapCache.hxx" #include "SlsCacheCompactor.hxx" #include "SlsBitmapCompressor.hxx" #include "SlsCacheConfiguration.hxx" #include // Define the default value for the maximal cache size that is used for // previews that are currently not visible. The visible previews are all // held in memory at all times. This default is used only when the // configuration does not have a value. const sal_Int32 MAXIMAL_CACHE_SIZE = 4*1024L*1024L; using namespace ::com::sun::star::uno; namespace sd::slidesorter::cache { class BitmapCache::CacheEntry { public: CacheEntry(const BitmapEx& rBitmap, sal_Int32 nLastAccessTime, bool bIsPrecious); CacheEntry(sal_Int32 nLastAccessTime, bool bIsPrecious); inline void Recycle (const CacheEntry& rEntry); inline sal_Int32 GetMemorySize() const; void Compress (const std::shared_ptr& rpCompressor); inline void Decompress(); bool IsUpToDate() const { return mbIsUpToDate; } void SetUpToDate (bool bIsUpToDate) { mbIsUpToDate = bIsUpToDate; } sal_Int32 GetAccessTime() const { return mnLastAccessTime; } void SetAccessTime (sal_Int32 nAccessTime) { mnLastAccessTime = nAccessTime; } const BitmapEx& GetPreview() const { return maPreview; } inline void SetPreview (const BitmapEx& rPreview); bool HasPreview() const; const BitmapEx& GetMarkedPreview() const { return maMarkedPreview; } inline void SetMarkedPreview (const BitmapEx& rMarkePreview); bool HasReplacement() const { return (mpReplacement != nullptr); } inline bool HasLosslessReplacement() const; void Invalidate() { mpReplacement.reset(); mpCompressor.reset(); mbIsUpToDate = false; } bool IsPrecious() const { return mbIsPrecious; } void SetPrecious (bool bIsPrecious) { mbIsPrecious = bIsPrecious; } private: BitmapEx maPreview; BitmapEx maMarkedPreview; std::shared_ptr mpReplacement; std::shared_ptr mpCompressor; bool mbIsUpToDate; sal_Int32 mnLastAccessTime; // When this flag is set then the bitmap is not modified by a cache // compactor. bool mbIsPrecious; }; namespace { class CacheHash { public: size_t operator()(const BitmapCache::CacheKey& p) const { return reinterpret_cast(p); } }; } class BitmapCache::CacheBitmapContainer : public std::unordered_map { public: CacheBitmapContainer() {} }; namespace { typedef ::std::vector< ::std::pair< ::sd::slidesorter::cache::BitmapCache::CacheKey, ::sd::slidesorter::cache::BitmapCache::CacheEntry> > SortableBitmapContainer; /** Compare elements of the bitmap cache according to their last access time. */ class AccessTimeComparator { public: bool operator () ( const SortableBitmapContainer::value_type& e1, const SortableBitmapContainer::value_type& e2) { return e1.second.GetAccessTime() < e2.second.GetAccessTime(); } }; } // end of anonymous namespace //===== BitmapCache ========================================================= BitmapCache::BitmapCache () : mpBitmapContainer(new CacheBitmapContainer()), mnNormalCacheSize(0), mnPreciousCacheSize(0), mnCurrentAccessTime(0), mnMaximalNormalCacheSize(MAXIMAL_CACHE_SIZE), mbIsFull(false) { Any aCacheSize (CacheConfiguration::Instance()->GetValue("CacheSize")); if (aCacheSize.has()) aCacheSize >>= mnMaximalNormalCacheSize; mpCacheCompactor = CacheCompactor::Create(*this,mnMaximalNormalCacheSize); } BitmapCache::~BitmapCache() { Clear(); } void BitmapCache::Clear() { ::osl::MutexGuard aGuard (maMutex); mpBitmapContainer->clear(); mnNormalCacheSize = 0; mnPreciousCacheSize = 0; mnCurrentAccessTime = 0; } bool BitmapCache::HasBitmap (const CacheKey& rKey) { ::osl::MutexGuard aGuard (maMutex); CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); return (iEntry != mpBitmapContainer->end() && (iEntry->second.HasPreview() || iEntry->second.HasReplacement())); } bool BitmapCache::BitmapIsUpToDate (const CacheKey& rKey) { ::osl::MutexGuard aGuard (maMutex); bool bIsUpToDate = false; CacheBitmapContainer::iterator aIterator (mpBitmapContainer->find(rKey)); if (aIterator != mpBitmapContainer->end()) bIsUpToDate = aIterator->second.IsUpToDate(); return bIsUpToDate; } BitmapEx BitmapCache::GetBitmap (const CacheKey& rKey) { ::osl::MutexGuard aGuard (maMutex); CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); if (iEntry == mpBitmapContainer->end()) { // Create an empty bitmap for the given key that acts as placeholder // until we are given the real one. Mark it as not being up to date. SetBitmap(rKey, BitmapEx(), false); iEntry = mpBitmapContainer->find(rKey); iEntry->second.SetUpToDate(false); } else { iEntry->second.SetAccessTime(mnCurrentAccessTime++); // Maybe we have to decompress the preview. if ( ! iEntry->second.HasPreview() && iEntry->second.HasReplacement()) { UpdateCacheSize(iEntry->second, REMOVE); iEntry->second.Decompress(); UpdateCacheSize(iEntry->second, ADD); } } return iEntry->second.GetPreview(); } BitmapEx BitmapCache::GetMarkedBitmap (const CacheKey& rKey) { ::osl::MutexGuard aGuard (maMutex); CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); if (iEntry != mpBitmapContainer->end()) { iEntry->second.SetAccessTime(mnCurrentAccessTime++); return iEntry->second.GetMarkedPreview(); } else return BitmapEx(); } void BitmapCache::ReleaseBitmap (const CacheKey& rKey) { ::osl::MutexGuard aGuard (maMutex); CacheBitmapContainer::iterator aIterator (mpBitmapContainer->find(rKey)); if (aIterator != mpBitmapContainer->end()) { UpdateCacheSize(aIterator->second, REMOVE); mpBitmapContainer->erase(aIterator); } } bool BitmapCache::InvalidateBitmap (const CacheKey& rKey) { ::osl::MutexGuard aGuard (maMutex); CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); if (iEntry != mpBitmapContainer->end()) { iEntry->second.SetUpToDate(false); // When there is a preview then we release the replacement. The // preview itself is kept until a new one is created. if (iEntry->second.HasPreview()) { UpdateCacheSize(iEntry->second, REMOVE); iEntry->second.Invalidate(); UpdateCacheSize(iEntry->second, ADD); } return true; } else return false; } void BitmapCache::InvalidateCache() { ::osl::MutexGuard aGuard (maMutex); for (auto& rEntry : *mpBitmapContainer) { rEntry.second.Invalidate(); } ReCalculateTotalCacheSize(); } void BitmapCache::SetBitmap ( const CacheKey& rKey, const BitmapEx& rPreview, bool bIsPrecious) { ::osl::MutexGuard aGuard (maMutex); CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); if (iEntry != mpBitmapContainer->end()) { UpdateCacheSize(iEntry->second, REMOVE); iEntry->second.SetPreview(rPreview); iEntry->second.SetUpToDate(true); iEntry->second.SetAccessTime(mnCurrentAccessTime++); } else { iEntry = mpBitmapContainer->emplace( rKey, CacheEntry(rPreview, mnCurrentAccessTime++, bIsPrecious) ).first; } if (iEntry != mpBitmapContainer->end()) UpdateCacheSize(iEntry->second, ADD); } void BitmapCache::SetMarkedBitmap ( const CacheKey& rKey, const BitmapEx& rPreview) { ::osl::MutexGuard aGuard (maMutex); CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); if (iEntry != mpBitmapContainer->end()) { UpdateCacheSize(iEntry->second, REMOVE); iEntry->second.SetMarkedPreview(rPreview); iEntry->second.SetAccessTime(mnCurrentAccessTime++); UpdateCacheSize(iEntry->second, ADD); } } void BitmapCache::SetPrecious (const CacheKey& rKey, bool bIsPrecious) { ::osl::MutexGuard aGuard (maMutex); CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); if (iEntry != mpBitmapContainer->end()) { if (iEntry->second.IsPrecious() != bIsPrecious) { UpdateCacheSize(iEntry->second, REMOVE); iEntry->second.SetPrecious(bIsPrecious); UpdateCacheSize(iEntry->second, ADD); } } else if (bIsPrecious) { iEntry = mpBitmapContainer->emplace( rKey, CacheEntry(BitmapEx(), mnCurrentAccessTime++, bIsPrecious) ).first; UpdateCacheSize(iEntry->second, ADD); } } void BitmapCache::ReCalculateTotalCacheSize() { ::osl::MutexGuard aGuard (maMutex); mnNormalCacheSize = 0; mnPreciousCacheSize = 0; for (const auto& rEntry : *mpBitmapContainer) { if (rEntry.second.IsPrecious()) mnPreciousCacheSize += rEntry.second.GetMemorySize(); else mnNormalCacheSize += rEntry.second.GetMemorySize(); } mbIsFull = (mnNormalCacheSize >= mnMaximalNormalCacheSize); SAL_INFO("sd.sls", __func__ << ": cache size is " << mnNormalCacheSize << "/" << mnPreciousCacheSize); } void BitmapCache::Recycle (const BitmapCache& rCache) { ::osl::MutexGuard aGuard (maMutex); for (const auto& rOtherEntry : *rCache.mpBitmapContainer) { CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rOtherEntry.first)); if (iEntry == mpBitmapContainer->end()) { iEntry = mpBitmapContainer->emplace( rOtherEntry.first, CacheEntry(mnCurrentAccessTime++, true) ).first; UpdateCacheSize(iEntry->second, ADD); } if (iEntry != mpBitmapContainer->end()) { UpdateCacheSize(iEntry->second, REMOVE); iEntry->second.Recycle(rOtherEntry.second); UpdateCacheSize(iEntry->second, ADD); } } } BitmapCache::CacheIndex BitmapCache::GetCacheIndex() const { ::osl::MutexGuard aGuard (maMutex); // Create a copy of the bitmap container. SortableBitmapContainer aSortedContainer; aSortedContainer.reserve(mpBitmapContainer->size()); // Copy the relevant entries. for (const auto& rEntry : *mpBitmapContainer) { if ( rEntry.second.IsPrecious()) continue; if ( ! rEntry.second.HasPreview()) continue; aSortedContainer.emplace_back(rEntry.first, rEntry.second); } // Sort the remaining entries. ::std::sort(aSortedContainer.begin(), aSortedContainer.end(), AccessTimeComparator()); // Return a list with the keys of the sorted entries. CacheIndex aIndex; aIndex.reserve(aSortedContainer.size()); for (const auto& rIndexEntry : aSortedContainer) aIndex.push_back(rIndexEntry.first); return aIndex; } void BitmapCache::Compress ( const CacheKey& rKey, const std::shared_ptr& rpCompressor) { ::osl::MutexGuard aGuard (maMutex); CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); if (iEntry != mpBitmapContainer->end() && iEntry->second.HasPreview()) { UpdateCacheSize(iEntry->second, REMOVE); iEntry->second.Compress(rpCompressor); UpdateCacheSize(iEntry->second, ADD); } } void BitmapCache::UpdateCacheSize (const CacheEntry& rEntry, CacheOperation eOperation) { sal_Int32 nEntrySize (rEntry.GetMemorySize()); sal_Int32& rCacheSize (rEntry.IsPrecious() ? mnPreciousCacheSize : mnNormalCacheSize); switch (eOperation) { case ADD: rCacheSize += nEntrySize; if ( ! rEntry.IsPrecious() && mnNormalCacheSize>mnMaximalNormalCacheSize) { mbIsFull = true; SAL_INFO("sd.sls", __func__ << ": cache size is " << mnNormalCacheSize << " > " << mnMaximalNormalCacheSize); mpCacheCompactor->RequestCompaction(); } break; case REMOVE: rCacheSize -= nEntrySize; if (mnNormalCacheSize < mnMaximalNormalCacheSize) mbIsFull = false; break; default: assert(false); break; } } //===== CacheEntry ============================================================ BitmapCache::CacheEntry::CacheEntry( sal_Int32 nLastAccessTime, bool bIsPrecious) : mbIsUpToDate(true), mnLastAccessTime(nLastAccessTime), mbIsPrecious(bIsPrecious) { } BitmapCache::CacheEntry::CacheEntry( const BitmapEx& rPreview, sal_Int32 nLastAccessTime, bool bIsPrecious) : maPreview(rPreview), mbIsUpToDate(true), mnLastAccessTime(nLastAccessTime), mbIsPrecious(bIsPrecious) { } inline void BitmapCache::CacheEntry::Recycle (const CacheEntry& rEntry) { if ((rEntry.HasPreview() || rEntry.HasLosslessReplacement()) && ! (HasPreview() || HasLosslessReplacement())) { maPreview = rEntry.maPreview; maMarkedPreview = rEntry.maMarkedPreview; mpReplacement = rEntry.mpReplacement; mpCompressor = rEntry.mpCompressor; mnLastAccessTime = rEntry.mnLastAccessTime; mbIsUpToDate = rEntry.mbIsUpToDate; } } inline sal_Int32 BitmapCache::CacheEntry::GetMemorySize() const { sal_Int32 nSize (0); nSize += maPreview.GetSizeBytes(); nSize += maMarkedPreview.GetSizeBytes(); if (mpReplacement != nullptr) nSize += mpReplacement->GetMemorySize(); return nSize; } void BitmapCache::CacheEntry::Compress (const std::shared_ptr& rpCompressor) { if ( maPreview.IsEmpty()) return; if (mpReplacement == nullptr) { mpReplacement = rpCompressor->Compress(maPreview); #ifdef DEBUG_SD_SLSBITMAPCACHE sal_uInt32 nOldSize (maPreview.GetSizeBytes()); sal_uInt32 nNewSize (mpReplacement.get()!=NULL ? mpReplacement->GetMemorySize() : 0); if (nOldSize == 0) nOldSize = 1; sal_Int32 nRatio (100L * nNewSize / nOldSize); SAL_INFO("sd.sls", __func__ << ": compressing bitmap for " << %x << " from " << nOldSize << " to " << nNewSize << " bytes (" << nRatio << "%)"); #endif mpCompressor = rpCompressor; } maPreview.SetEmpty(); maMarkedPreview.SetEmpty(); } inline void BitmapCache::CacheEntry::Decompress() { if (mpReplacement != nullptr && mpCompressor != nullptr && maPreview.IsEmpty()) { maPreview = mpCompressor->Decompress(*mpReplacement); maMarkedPreview.SetEmpty(); if ( ! mpCompressor->IsLossless()) mbIsUpToDate = false; } } inline void BitmapCache::CacheEntry::SetPreview (const BitmapEx& rPreview) { maPreview = rPreview; maMarkedPreview.SetEmpty(); mpReplacement.reset(); mpCompressor.reset(); } bool BitmapCache::CacheEntry::HasPreview() const { return ! maPreview.IsEmpty(); } inline void BitmapCache::CacheEntry::SetMarkedPreview (const BitmapEx& rMarkedPreview) { maMarkedPreview = rMarkedPreview; } inline bool BitmapCache::CacheEntry::HasLosslessReplacement() const { return mpReplacement != nullptr && mpCompressor != nullptr && mpCompressor->IsLossless(); } } // end of namespace ::sd::slidesorter::cache /* vim:set shiftwidth=4 softtabstop=4 expandtab: */