1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <unordered_map>
22 #include "SlsBitmapCache.hxx"
23 #include "SlsCacheCompactor.hxx"
24 #include "SlsBitmapCompressor.hxx"
25 #include "SlsCacheConfiguration.hxx"
27 #include <osl/diagnose.h>
28 #include <sal/log.hxx>
30 // Define the default value for the maximal cache size that is used for
31 // previews that are currently not visible. The visible previews are all
32 // held in memory at all times. This default is used only when the
33 // configuration does not have a value.
34 const sal_Int32 MAXIMAL_CACHE_SIZE
= 4*1024L*1024L;
36 using namespace ::com::sun::star::uno
;
38 namespace sd::slidesorter::cache
{
40 class BitmapCache::CacheEntry
43 CacheEntry(const BitmapEx
& rBitmap
, sal_Int32 nLastAccessTime
, bool bIsPrecious
);
44 CacheEntry(sal_Int32 nLastAccessTime
, bool bIsPrecious
);
45 inline void Recycle (const CacheEntry
& rEntry
);
46 inline sal_Int32
GetMemorySize() const;
47 void Compress (const std::shared_ptr
<BitmapCompressor
>& rpCompressor
);
48 inline void Decompress();
50 bool IsUpToDate() const { return mbIsUpToDate
; }
51 void SetUpToDate (bool bIsUpToDate
) { mbIsUpToDate
= bIsUpToDate
; }
52 sal_Int32
GetAccessTime() const { return mnLastAccessTime
; }
53 void SetAccessTime (sal_Int32 nAccessTime
) { mnLastAccessTime
= nAccessTime
; }
55 const BitmapEx
& GetPreview() const { return maPreview
; }
56 inline void SetPreview (const BitmapEx
& rPreview
);
57 bool HasPreview() const;
59 const BitmapEx
& GetMarkedPreview() const { return maMarkedPreview
; }
60 inline void SetMarkedPreview (const BitmapEx
& rMarkePreview
);
62 bool HasReplacement() const { return (mpReplacement
!= nullptr); }
63 inline bool HasLosslessReplacement() const;
64 void Invalidate() { mpReplacement
.reset(); mpCompressor
.reset(); mbIsUpToDate
= false; }
65 bool IsPrecious() const { return mbIsPrecious
; }
66 void SetPrecious (bool bIsPrecious
) { mbIsPrecious
= bIsPrecious
; }
70 BitmapEx maMarkedPreview
;
71 std::shared_ptr
<BitmapReplacement
> mpReplacement
;
72 std::shared_ptr
<BitmapCompressor
> mpCompressor
;
74 sal_Int32 mnLastAccessTime
;
75 // When this flag is set then the bitmap is not modified by a cache
84 size_t operator()(const BitmapCache::CacheKey
& p
) const
85 { return reinterpret_cast<size_t>(p
); }
90 class BitmapCache::CacheBitmapContainer
91 : public std::unordered_map
<CacheKey
, CacheEntry
, CacheHash
>
94 CacheBitmapContainer() {}
99 typedef ::std::vector
<
100 ::std::pair
< ::sd::slidesorter::cache::BitmapCache::CacheKey
,
101 ::sd::slidesorter::cache::BitmapCache::CacheEntry
>
102 > SortableBitmapContainer
;
104 /** Compare elements of the bitmap cache according to their last access
107 class AccessTimeComparator
111 const SortableBitmapContainer::value_type
& e1
,
112 const SortableBitmapContainer::value_type
& e2
)
114 return e1
.second
.GetAccessTime() < e2
.second
.GetAccessTime();
118 } // end of anonymous namespace
120 //===== BitmapCache =========================================================
122 BitmapCache::BitmapCache ()
124 mpBitmapContainer(new CacheBitmapContainer()),
125 mnNormalCacheSize(0),
126 mnPreciousCacheSize(0),
127 mnCurrentAccessTime(0),
128 mnMaximalNormalCacheSize(MAXIMAL_CACHE_SIZE
),
132 Any
aCacheSize (CacheConfiguration::Instance()->GetValue("CacheSize"));
133 if (aCacheSize
.has
<sal_Int32
>())
134 aCacheSize
>>= mnMaximalNormalCacheSize
;
136 mpCacheCompactor
= CacheCompactor::Create(*this,mnMaximalNormalCacheSize
);
139 BitmapCache::~BitmapCache()
144 void BitmapCache::Clear()
146 ::osl::MutexGuard
aGuard (maMutex
);
148 mpBitmapContainer
->clear();
149 mnNormalCacheSize
= 0;
150 mnPreciousCacheSize
= 0;
151 mnCurrentAccessTime
= 0;
154 bool BitmapCache::HasBitmap (const CacheKey
& rKey
)
156 ::osl::MutexGuard
aGuard (maMutex
);
158 CacheBitmapContainer::iterator
iEntry (mpBitmapContainer
->find(rKey
));
159 return (iEntry
!= mpBitmapContainer
->end()
160 && (iEntry
->second
.HasPreview() || iEntry
->second
.HasReplacement()));
163 bool BitmapCache::BitmapIsUpToDate (const CacheKey
& rKey
)
165 ::osl::MutexGuard
aGuard (maMutex
);
167 bool bIsUpToDate
= false;
168 CacheBitmapContainer::iterator
aIterator (mpBitmapContainer
->find(rKey
));
169 if (aIterator
!= mpBitmapContainer
->end())
170 bIsUpToDate
= aIterator
->second
.IsUpToDate();
175 BitmapEx
BitmapCache::GetBitmap (const CacheKey
& rKey
)
177 ::osl::MutexGuard
aGuard (maMutex
);
179 CacheBitmapContainer::iterator
iEntry (mpBitmapContainer
->find(rKey
));
180 if (iEntry
== mpBitmapContainer
->end())
182 // Create an empty bitmap for the given key that acts as placeholder
183 // until we are given the real one. Mark it as not being up to date.
184 SetBitmap(rKey
, BitmapEx(), false);
185 iEntry
= mpBitmapContainer
->find(rKey
);
186 iEntry
->second
.SetUpToDate(false);
190 iEntry
->second
.SetAccessTime(mnCurrentAccessTime
++);
192 // Maybe we have to decompress the preview.
193 if ( ! iEntry
->second
.HasPreview() && iEntry
->second
.HasReplacement())
195 UpdateCacheSize(iEntry
->second
, REMOVE
);
196 iEntry
->second
.Decompress();
197 UpdateCacheSize(iEntry
->second
, ADD
);
200 return iEntry
->second
.GetPreview();
203 BitmapEx
BitmapCache::GetMarkedBitmap (const CacheKey
& rKey
)
205 ::osl::MutexGuard
aGuard (maMutex
);
207 CacheBitmapContainer::iterator
iEntry (mpBitmapContainer
->find(rKey
));
208 if (iEntry
!= mpBitmapContainer
->end())
210 iEntry
->second
.SetAccessTime(mnCurrentAccessTime
++);
211 return iEntry
->second
.GetMarkedPreview();
217 void BitmapCache::ReleaseBitmap (const CacheKey
& rKey
)
219 ::osl::MutexGuard
aGuard (maMutex
);
221 CacheBitmapContainer::iterator
aIterator (mpBitmapContainer
->find(rKey
));
222 if (aIterator
!= mpBitmapContainer
->end())
224 UpdateCacheSize(aIterator
->second
, REMOVE
);
225 mpBitmapContainer
->erase(aIterator
);
229 bool BitmapCache::InvalidateBitmap (const CacheKey
& rKey
)
231 ::osl::MutexGuard
aGuard (maMutex
);
233 CacheBitmapContainer::iterator
iEntry (mpBitmapContainer
->find(rKey
));
234 if (iEntry
!= mpBitmapContainer
->end())
236 iEntry
->second
.SetUpToDate(false);
238 // When there is a preview then we release the replacement. The
239 // preview itself is kept until a new one is created.
240 if (iEntry
->second
.HasPreview())
242 UpdateCacheSize(iEntry
->second
, REMOVE
);
243 iEntry
->second
.Invalidate();
244 UpdateCacheSize(iEntry
->second
, ADD
);
252 void BitmapCache::InvalidateCache()
254 ::osl::MutexGuard
aGuard (maMutex
);
256 for (auto& rEntry
: *mpBitmapContainer
)
258 rEntry
.second
.Invalidate();
260 ReCalculateTotalCacheSize();
263 void BitmapCache::SetBitmap (
264 const CacheKey
& rKey
,
265 const BitmapEx
& rPreview
,
268 ::osl::MutexGuard
aGuard (maMutex
);
270 CacheBitmapContainer::iterator
iEntry (mpBitmapContainer
->find(rKey
));
271 if (iEntry
!= mpBitmapContainer
->end())
273 UpdateCacheSize(iEntry
->second
, REMOVE
);
274 iEntry
->second
.SetPreview(rPreview
);
275 iEntry
->second
.SetUpToDate(true);
276 iEntry
->second
.SetAccessTime(mnCurrentAccessTime
++);
280 iEntry
= mpBitmapContainer
->emplace(
282 CacheEntry(rPreview
, mnCurrentAccessTime
++, bIsPrecious
)
286 if (iEntry
!= mpBitmapContainer
->end())
287 UpdateCacheSize(iEntry
->second
, ADD
);
290 void BitmapCache::SetMarkedBitmap (
291 const CacheKey
& rKey
,
292 const BitmapEx
& rPreview
)
294 ::osl::MutexGuard
aGuard (maMutex
);
296 CacheBitmapContainer::iterator
iEntry (mpBitmapContainer
->find(rKey
));
297 if (iEntry
!= mpBitmapContainer
->end())
299 UpdateCacheSize(iEntry
->second
, REMOVE
);
300 iEntry
->second
.SetMarkedPreview(rPreview
);
301 iEntry
->second
.SetAccessTime(mnCurrentAccessTime
++);
302 UpdateCacheSize(iEntry
->second
, ADD
);
306 void BitmapCache::SetPrecious (const CacheKey
& rKey
, bool bIsPrecious
)
308 ::osl::MutexGuard
aGuard (maMutex
);
310 CacheBitmapContainer::iterator
iEntry (mpBitmapContainer
->find(rKey
));
311 if (iEntry
!= mpBitmapContainer
->end())
313 if (iEntry
->second
.IsPrecious() != bIsPrecious
)
315 UpdateCacheSize(iEntry
->second
, REMOVE
);
316 iEntry
->second
.SetPrecious(bIsPrecious
);
317 UpdateCacheSize(iEntry
->second
, ADD
);
320 else if (bIsPrecious
)
322 iEntry
= mpBitmapContainer
->emplace(
324 CacheEntry(BitmapEx(), mnCurrentAccessTime
++, bIsPrecious
)
326 UpdateCacheSize(iEntry
->second
, ADD
);
330 void BitmapCache::ReCalculateTotalCacheSize()
332 ::osl::MutexGuard
aGuard (maMutex
);
334 mnNormalCacheSize
= 0;
335 mnPreciousCacheSize
= 0;
336 for (const auto& rEntry
: *mpBitmapContainer
)
338 if (rEntry
.second
.IsPrecious())
339 mnPreciousCacheSize
+= rEntry
.second
.GetMemorySize();
341 mnNormalCacheSize
+= rEntry
.second
.GetMemorySize();
343 mbIsFull
= (mnNormalCacheSize
>= mnMaximalNormalCacheSize
);
345 SAL_INFO("sd.sls", __func__
<< ": cache size is " << mnNormalCacheSize
<< "/" << mnPreciousCacheSize
);
348 void BitmapCache::Recycle (const BitmapCache
& rCache
)
350 ::osl::MutexGuard
aGuard (maMutex
);
352 for (const auto& rOtherEntry
: *rCache
.mpBitmapContainer
)
354 CacheBitmapContainer::iterator
iEntry (mpBitmapContainer
->find(rOtherEntry
.first
));
355 if (iEntry
== mpBitmapContainer
->end())
357 iEntry
= mpBitmapContainer
->emplace(
359 CacheEntry(mnCurrentAccessTime
++, true)
361 UpdateCacheSize(iEntry
->second
, ADD
);
363 if (iEntry
!= mpBitmapContainer
->end())
365 UpdateCacheSize(iEntry
->second
, REMOVE
);
366 iEntry
->second
.Recycle(rOtherEntry
.second
);
367 UpdateCacheSize(iEntry
->second
, ADD
);
372 ::std::unique_ptr
<BitmapCache::CacheIndex
> BitmapCache::GetCacheIndex() const
374 ::osl::MutexGuard
aGuard (maMutex
);
376 // Create a copy of the bitmap container.
377 SortableBitmapContainer aSortedContainer
;
378 aSortedContainer
.reserve(mpBitmapContainer
->size());
380 // Copy the relevant entries.
381 for (const auto& rEntry
: *mpBitmapContainer
)
383 if ( rEntry
.second
.IsPrecious())
386 if ( ! rEntry
.second
.HasPreview())
389 aSortedContainer
.emplace_back(rEntry
.first
, rEntry
.second
);
392 // Sort the remaining entries.
393 ::std::sort(aSortedContainer
.begin(), aSortedContainer
.end(), AccessTimeComparator());
395 // Return a list with the keys of the sorted entries.
396 ::std::unique_ptr
<CacheIndex
> pIndex(new CacheIndex
);
397 pIndex
->reserve(aSortedContainer
.size());
398 for (const auto& rIndexEntry
: aSortedContainer
)
399 pIndex
->push_back(rIndexEntry
.first
);
403 void BitmapCache::Compress (
404 const CacheKey
& rKey
,
405 const std::shared_ptr
<BitmapCompressor
>& rpCompressor
)
407 ::osl::MutexGuard
aGuard (maMutex
);
409 CacheBitmapContainer::iterator
iEntry (mpBitmapContainer
->find(rKey
));
410 if (iEntry
!= mpBitmapContainer
->end() && iEntry
->second
.HasPreview())
412 UpdateCacheSize(iEntry
->second
, REMOVE
);
413 iEntry
->second
.Compress(rpCompressor
);
414 UpdateCacheSize(iEntry
->second
, ADD
);
418 void BitmapCache::UpdateCacheSize (const CacheEntry
& rEntry
, CacheOperation eOperation
)
420 sal_Int32
nEntrySize (rEntry
.GetMemorySize());
421 sal_Int32
& rCacheSize (rEntry
.IsPrecious() ? mnPreciousCacheSize
: mnNormalCacheSize
);
425 rCacheSize
+= nEntrySize
;
426 if ( ! rEntry
.IsPrecious() && mnNormalCacheSize
>mnMaximalNormalCacheSize
)
429 SAL_INFO("sd.sls", __func__
<< ": cache size is " << mnNormalCacheSize
<< " > " << mnMaximalNormalCacheSize
);
430 mpCacheCompactor
->RequestCompaction();
435 rCacheSize
-= nEntrySize
;
436 if (mnNormalCacheSize
< mnMaximalNormalCacheSize
)
446 //===== CacheEntry ============================================================
448 BitmapCache::CacheEntry::CacheEntry(
449 sal_Int32 nLastAccessTime
,
454 mnLastAccessTime(nLastAccessTime
),
455 mbIsPrecious(bIsPrecious
)
459 BitmapCache::CacheEntry::CacheEntry(
460 const BitmapEx
& rPreview
,
461 sal_Int32 nLastAccessTime
,
463 : maPreview(rPreview
),
466 mnLastAccessTime(nLastAccessTime
),
467 mbIsPrecious(bIsPrecious
)
471 inline void BitmapCache::CacheEntry::Recycle (const CacheEntry
& rEntry
)
473 if ((rEntry
.HasPreview() || rEntry
.HasLosslessReplacement())
474 && ! (HasPreview() || HasLosslessReplacement()))
476 maPreview
= rEntry
.maPreview
;
477 maMarkedPreview
= rEntry
.maMarkedPreview
;
478 mpReplacement
= rEntry
.mpReplacement
;
479 mpCompressor
= rEntry
.mpCompressor
;
480 mnLastAccessTime
= rEntry
.mnLastAccessTime
;
481 mbIsUpToDate
= rEntry
.mbIsUpToDate
;
485 inline sal_Int32
BitmapCache::CacheEntry::GetMemorySize() const
488 nSize
+= maPreview
.GetSizeBytes();
489 nSize
+= maMarkedPreview
.GetSizeBytes();
490 if (mpReplacement
!= nullptr)
491 nSize
+= mpReplacement
->GetMemorySize();
495 void BitmapCache::CacheEntry::Compress (const std::shared_ptr
<BitmapCompressor
>& rpCompressor
)
497 if ( maPreview
.IsEmpty())
500 if (mpReplacement
== nullptr)
502 mpReplacement
= rpCompressor
->Compress(maPreview
);
504 #ifdef DEBUG_SD_SLSBITMAPCACHE
505 sal_uInt32
nOldSize (maPreview
.GetSizeBytes());
506 sal_uInt32
nNewSize (mpReplacement
.get()!=NULL
? mpReplacement
->GetMemorySize() : 0);
509 sal_Int32
nRatio (100L * nNewSize
/ nOldSize
);
510 SAL_INFO("sd.sls", __func__
<< ": compressing bitmap for " << %x
<< " from " << nOldSize
<< " to " << nNewSize
<< " bytes (" << nRatio
<< "%)");
513 mpCompressor
= rpCompressor
;
516 maPreview
.SetEmpty();
517 maMarkedPreview
.SetEmpty();
520 inline void BitmapCache::CacheEntry::Decompress()
522 if (mpReplacement
!= nullptr && mpCompressor
!= nullptr && maPreview
.IsEmpty())
524 maPreview
= mpCompressor
->Decompress(*mpReplacement
);
525 maMarkedPreview
.SetEmpty();
526 if ( ! mpCompressor
->IsLossless())
527 mbIsUpToDate
= false;
531 inline void BitmapCache::CacheEntry::SetPreview (const BitmapEx
& rPreview
)
533 maPreview
= rPreview
;
534 maMarkedPreview
.SetEmpty();
535 mpReplacement
.reset();
536 mpCompressor
.reset();
539 bool BitmapCache::CacheEntry::HasPreview() const
541 return ! maPreview
.IsEmpty();
544 inline void BitmapCache::CacheEntry::SetMarkedPreview (const BitmapEx
& rMarkedPreview
)
546 maMarkedPreview
= rMarkedPreview
;
549 inline bool BitmapCache::CacheEntry::HasLosslessReplacement() const
551 return mpReplacement
!= nullptr && mpCompressor
!= nullptr && mpCompressor
->IsLossless();
554 } // end of namespace ::sd::slidesorter::cache
556 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */