1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qpixmapcache.h"
5#include "qobject.h"
6#include "qdebug.h"
7#include "qpixmapcache_p.h"
8#include "qthread.h"
9#include "qcoreapplication.h"
10
11using namespace std::chrono_literals;
12
13QT_BEGIN_NAMESPACE
14
15/*!
16 \class QPixmapCache
17 \inmodule QtGui
18
19 \brief The QPixmapCache class provides an application-wide cache for pixmaps.
20
21 This class is a tool for optimized drawing with QPixmap. You can
22 use it to store temporary pixmaps that are expensive to generate
23 without using more storage space than cacheLimit(). Use insert()
24 to insert pixmaps, find() to find them, and clear() to empty the
25 cache.
26
27 QPixmapCache contains no member data, only static functions to
28 access the global pixmap cache. It creates an internal QCache
29 object for caching the pixmaps.
30
31 The cache associates a pixmap with a user-provided string as a key,
32 or with a QPixmapCache::Key that the cache generates.
33 Using QPixmapCache::Key for keys is faster than using strings. The string API is
34 very convenient for complex keys but the QPixmapCache::Key API will be very
35 efficient and convenient for a one-to-one object-to-pixmap mapping - in
36 this case, you can store the keys as members of an object.
37
38 If two pixmaps are inserted into the cache using equal keys then the
39 last pixmap will replace the first pixmap in the cache. This follows the
40 behavior of the QHash and QCache classes.
41
42 The cache becomes full when the total size of all pixmaps in the
43 cache exceeds cacheLimit(). The initial cache limit is 10240 KB (10 MB);
44 you can change this by calling setCacheLimit() with the required value.
45 A pixmap takes roughly (\e{width} * \e{height} * \e{depth})/8 bytes of
46 memory.
47
48 The \e{Qt Quarterly} article
49 \l{http://doc.qt.io/archives/qq/qq12-qpixmapcache.html}{Optimizing
50 with QPixmapCache} explains how to use QPixmapCache to speed up
51 applications by caching the results of painting.
52
53 \note QPixmapCache is only usable from the application's main thread.
54 Access from other threads will be ignored and return failure.
55
56 \sa QCache, QPixmap
57*/
58
59static const int cache_limit_default = 10240; // 10 MB cache limit
60
61static inline qsizetype cost(const QPixmap &pixmap)
62{
63 // make sure to do a 64bit calculation; qsizetype might be smaller
64 const qint64 costKb = static_cast<qint64>(pixmap.width())
65 * pixmap.height() * pixmap.depth() / (8 * 1024);
66 const qint64 costMax = std::numeric_limits<qsizetype>::max();
67 // a small pixmap should have at least a cost of 1(kb)
68 return static_cast<qsizetype>(qBound(min: 1LL, val: costKb, max: costMax));
69}
70
71static inline bool qt_pixmapcache_thread_test()
72{
73 if (Q_LIKELY(QCoreApplication::instance() && QThread::currentThread() == QCoreApplication::instance()->thread()))
74 return true;
75
76 return false;
77}
78
79/*!
80 \class QPixmapCache::Key
81 \brief The QPixmapCache::Key class can be used for efficient access
82 to the QPixmapCache.
83 \inmodule QtGui
84 \since 4.6
85
86 Use QPixmapCache::insert() to receive an instance of Key generated
87 by the pixmap cache. You can store the key in your own objects for
88 a very efficient one-to-one object-to-pixmap mapping.
89*/
90
91/*!
92 Constructs an empty Key object.
93*/
94QPixmapCache::Key::Key() : d(nullptr)
95{
96}
97
98/*!
99 \internal
100 Constructs a copy of \a other.
101*/
102QPixmapCache::Key::Key(const Key &other)
103{
104 if (other.d)
105 ++(other.d->ref);
106 d = other.d;
107}
108
109/*!
110 Destroys the key.
111*/
112QPixmapCache::Key::~Key()
113{
114 if (d && --(d->ref) == 0)
115 delete d;
116}
117
118/*!
119 \internal
120
121 Returns \c true if this key is the same as the given \a key; otherwise returns
122 false.
123*/
124bool QPixmapCache::Key::operator ==(const Key &key) const
125{
126 return (d == key.d);
127}
128
129/*!
130 \fn bool QPixmapCache::Key::operator !=(const Key &key) const
131 \internal
132*/
133
134/*!
135 \fn QPixmapCache::Key::Key(Key &&)
136 \internal
137 \since 5.6
138*/
139
140/*!
141 \fn QPixmapCache::Key &QPixmapCache::Key::operator=(Key &&)
142 \internal
143 \since 5.6
144*/
145
146/*!
147 \fn void QPixmapCache::Key::swap(Key &)
148 \internal
149 \since 5.6
150*/
151
152/*!
153 Returns \c true if there is a cached pixmap associated with this key.
154 Otherwise, if pixmap was flushed, the key is no longer valid.
155 \since 5.7
156*/
157bool QPixmapCache::Key::isValid() const noexcept
158{
159 return d && d->isValid;
160}
161
162/*!
163 \internal
164*/
165QPixmapCache::Key &QPixmapCache::Key::operator =(const Key &other)
166{
167 if (d != other.d) {
168 if (other.d)
169 ++(other.d->ref);
170 if (d && --(d->ref) == 0)
171 delete d;
172 d = other.d;
173 }
174 return *this;
175}
176
177class QPMCache : public QObject, public QCache<QPixmapCache::Key, QPixmapCacheEntry>
178{
179 Q_OBJECT
180public:
181 QPMCache();
182 ~QPMCache();
183
184 void timerEvent(QTimerEvent *) override;
185 bool insert(const QString& key, const QPixmap &pixmap, int cost);
186 QPixmapCache::Key insert(const QPixmap &pixmap, int cost);
187 bool remove(const QString &key);
188 bool remove(const QPixmapCache::Key &key);
189
190 void resizeKeyArray(int size);
191 QPixmapCache::Key createKey();
192 void releaseKey(const QPixmapCache::Key &key);
193 void clear();
194
195 QPixmap *object(const QString &key) const;
196 QPixmap *object(const QPixmapCache::Key &key) const;
197
198 static inline QPixmapCache::KeyData *get(const QPixmapCache::Key &key)
199 {return key.d;}
200
201 static QPixmapCache::KeyData* getKeyData(QPixmapCache::Key *key);
202
203 bool flushDetachedPixmaps(bool nt);
204
205private:
206 static constexpr auto soon_time = 10s;
207 static constexpr auto flush_time = 30s;
208 int *keyArray;
209 int theid;
210 int ps;
211 int keyArraySize;
212 int freeKey;
213 QHash<QString, QPixmapCache::Key> cacheKeys;
214 bool t;
215};
216
217QT_BEGIN_INCLUDE_NAMESPACE
218#include "qpixmapcache.moc"
219QT_END_INCLUDE_NAMESPACE
220
221/*!
222 size_t QPixmapCache::qHash(const Key &key, size_t seed = 0);
223 \since 6.6
224
225 Returns the hash value for the \a key, using \a seed to seed the calculation.
226*/
227size_t QPixmapCache::Key::hash(size_t seed) const noexcept
228{
229 return qHash(key: this->d ? this->d->key : 0, seed);
230}
231
232QPMCache::QPMCache()
233 : QObject(nullptr),
234 QCache<QPixmapCache::Key, QPixmapCacheEntry>(cache_limit_default),
235 keyArray(nullptr), theid(0), ps(0), keyArraySize(0), freeKey(0), t(false)
236{
237}
238QPMCache::~QPMCache()
239{
240 clear();
241 free(ptr: keyArray);
242}
243
244/*
245 This is supposed to cut the cache size down by about 25% in a
246 minute once the application becomes idle, to let any inserted pixmap
247 remain in the cache for some time before it becomes a candidate for
248 cleaning-up, and to not cut down the size of the cache while the
249 cache is in active use.
250
251 When the last detached pixmap has been deleted from the cache, kill the
252 timer so Qt won't keep the CPU from going into sleep mode. Currently
253 the timer is not restarted when the pixmap becomes unused, but it does
254 restart once something else is added (i.e. the cache space is actually needed).
255
256 Returns \c true if any were removed.
257*/
258bool QPMCache::flushDetachedPixmaps(bool nt)
259{
260 auto mc = maxCost();
261 const qsizetype currentTotal = totalCost();
262 const qsizetype oldSize = size();
263 if (currentTotal)
264 setMaxCost(nt ? currentTotal * 3 / 4 : currentTotal - 1);
265 setMaxCost(mc);
266 ps = totalCost();
267 return size() != oldSize;
268}
269
270void QPMCache::timerEvent(QTimerEvent *)
271{
272 bool nt = totalCost() == ps;
273 if (!flushDetachedPixmaps(nt)) {
274 killTimer(id: theid);
275 theid = 0;
276 } else if (nt != t) {
277 killTimer(id: theid);
278 theid = startTimer(time: nt ? soon_time : flush_time);
279 t = nt;
280 }
281}
282
283
284QPixmap *QPMCache::object(const QString &key) const
285{
286 if (const auto it = cacheKeys.find(key); it != cacheKeys.cend())
287 return object(key: it.value());
288 return nullptr;
289}
290
291QPixmap *QPMCache::object(const QPixmapCache::Key &key) const
292{
293 Q_ASSERT(key.isValid());
294 QPixmap *ptr = QCache<QPixmapCache::Key, QPixmapCacheEntry>::object(key);
295 //We didn't find the pixmap in the cache, the key is not valid anymore
296 if (!ptr)
297 const_cast<QPMCache *>(this)->releaseKey(key);
298 return ptr;
299}
300
301bool QPMCache::insert(const QString& key, const QPixmap &pixmap, int cost)
302{
303 //If for the same key we add already a pixmap we should delete it
304 remove(key);
305
306 // this will create a new key; the old one has been removed
307 auto k = insert(pixmap, cost);
308 if (k.isValid()) {
309 k.d->stringKey = key;
310 cacheKeys[key] = std::move(k);
311 return true;
312 }
313 return false;
314}
315
316QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost)
317{
318 QPixmapCache::Key cacheKey = createKey(); // invalidated by ~QPixmapCacheEntry on failed insert
319 bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(key: cacheKey, object: new QPixmapCacheEntry(cacheKey, pixmap), cost);
320 Q_ASSERT(success || !cacheKey.isValid());
321 if (success) {
322 if (!theid) {
323 theid = startTimer(time: flush_time);
324 t = false;
325 }
326 }
327 return cacheKey;
328}
329
330bool QPMCache::remove(const QString &key)
331{
332 const auto cacheKey = cacheKeys.take(key);
333 return cacheKey.isValid() && remove(key: cacheKey);
334}
335
336bool QPMCache::remove(const QPixmapCache::Key &key)
337{
338 return QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
339}
340
341void QPMCache::resizeKeyArray(int size)
342{
343 if (size <= keyArraySize || size == 0)
344 return;
345 keyArray = q_check_ptr(p: static_cast<int *>(realloc(ptr: keyArray,
346 size: size * sizeof(int))));
347 for (int i = keyArraySize; i != size; ++i)
348 keyArray[i] = i + 1;
349 keyArraySize = size;
350}
351
352QPixmapCache::Key QPMCache::createKey()
353{
354 if (freeKey == keyArraySize)
355 resizeKeyArray(size: keyArraySize ? keyArraySize << 1 : 2);
356 int id = freeKey;
357 freeKey = keyArray[id];
358 QPixmapCache::Key key;
359 QPixmapCache::KeyData *d = QPMCache::getKeyData(key: &key);
360 d->key = ++id;
361 return key;
362}
363
364void QPMCache::releaseKey(const QPixmapCache::Key &key)
365{
366 QPixmapCache::KeyData *keyData = key.d;
367 if (!keyData)
368 return;
369 if (!keyData->stringKey.isNull())
370 cacheKeys.remove(key: keyData->stringKey);
371 if (keyData->key > keyArraySize || keyData->key <= 0)
372 return;
373 keyData->key--;
374 keyArray[keyData->key] = freeKey;
375 freeKey = keyData->key;
376 keyData->isValid = false;
377 keyData->key = 0;
378}
379
380void QPMCache::clear()
381{
382 free(ptr: keyArray);
383 keyArray = nullptr;
384 freeKey = 0;
385 keyArraySize = 0;
386 //Mark all keys as invalid
387 const QList<QPixmapCache::Key> keys = QCache<QPixmapCache::Key, QPixmapCacheEntry>::keys();
388 for (const auto &key : keys) {
389 if (key.d)
390 key.d->isValid = false;
391 }
392 QCache<QPixmapCache::Key, QPixmapCacheEntry>::clear();
393 // Nothing left to flush; stop the timer
394 if (theid) {
395 killTimer(id: theid);
396 theid = 0;
397 }
398}
399
400QPixmapCache::KeyData* QPMCache::getKeyData(QPixmapCache::Key *key)
401{
402 if (!key->d)
403 key->d = new QPixmapCache::KeyData;
404 return key->d;
405}
406
407Q_GLOBAL_STATIC(QPMCache, pm_cache)
408
409int Q_AUTOTEST_EXPORT q_QPixmapCache_keyHashSize()
410{
411 return pm_cache()->size();
412}
413
414QPixmapCacheEntry::~QPixmapCacheEntry()
415{
416 pm_cache()->releaseKey(key);
417}
418
419/*!
420 Looks for a cached pixmap associated with the given \a key in the cache.
421 If the pixmap is found, the function sets \a pixmap to that pixmap and
422 returns \c true; otherwise it leaves \a pixmap alone and returns \c false.
423
424 \since 4.6
425
426 Example:
427 \snippet code/src_gui_image_qpixmapcache.cpp 1
428*/
429
430bool QPixmapCache::find(const QString &key, QPixmap *pixmap)
431{
432 if (key.isEmpty() || !qt_pixmapcache_thread_test())
433 return false;
434 QPixmap *ptr = pm_cache()->object(key);
435 if (ptr && pixmap)
436 *pixmap = *ptr;
437 return ptr != nullptr;
438}
439
440/*!
441 Looks for a cached pixmap associated with the given \a key in the cache.
442 If the pixmap is found, the function sets \a pixmap to that pixmap and
443 returns \c true; otherwise it leaves \a pixmap alone and returns \c false. If
444 the pixmap is not found, it means that the \a key is no longer valid,
445 so it will be released for the next insertion.
446
447 \since 4.6
448*/
449bool QPixmapCache::find(const Key &key, QPixmap *pixmap)
450{
451 if (!qt_pixmapcache_thread_test())
452 return false;
453 //The key is not valid anymore, a flush happened before probably
454 if (!key.d || !key.d->isValid)
455 return false;
456 QPixmap *ptr = pm_cache()->object(key);
457 if (ptr && pixmap)
458 *pixmap = *ptr;
459 return ptr != nullptr;
460}
461
462/*!
463 Inserts a copy of the pixmap \a pixmap associated with the \a key into
464 the cache.
465
466 All pixmaps inserted by the Qt library have a key starting with
467 "$qt", so your own pixmap keys should never begin "$qt".
468
469 When a pixmap is inserted and the cache is about to exceed its
470 limit, it removes pixmaps until there is enough room for the
471 pixmap to be inserted.
472
473 The oldest pixmaps (least recently accessed in the cache) are
474 deleted when more space is needed.
475
476 The function returns \c true if the object was inserted into the
477 cache; otherwise it returns \c false.
478
479 \sa setCacheLimit()
480*/
481
482bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap)
483{
484 if (key.isEmpty() || !qt_pixmapcache_thread_test())
485 return false;
486 return pm_cache()->insert(key, pixmap, cost: cost(pixmap));
487}
488
489/*!
490 Inserts a copy of the given \a pixmap into the cache and returns a key
491 that can be used to retrieve it.
492
493 When a pixmap is inserted and the cache is about to exceed its
494 limit, it removes pixmaps until there is enough room for the
495 pixmap to be inserted.
496
497 The oldest pixmaps (least recently accessed in the cache) are
498 deleted when more space is needed.
499
500 \sa setCacheLimit(), replace()
501
502 \since 4.6
503*/
504QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap)
505{
506 if (!qt_pixmapcache_thread_test())
507 return QPixmapCache::Key();
508 return pm_cache()->insert(pixmap, cost: cost(pixmap));
509}
510
511#if QT_DEPRECATED_SINCE(6, 6)
512/*!
513 \fn bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap)
514
515 \deprecated [6.6] Use \c{remove(key); key = insert(pixmap);} instead.
516
517 Replaces the pixmap associated with the given \a key with the \a pixmap
518 specified. Returns \c true if the \a pixmap has been correctly inserted into
519 the cache; otherwise returns \c false.
520
521 The passed \a key is updated to reference \a pixmap now. Other copies of \a
522 key, if any, still refer to the old pixmap, which is, however, removed from
523 the cache by this function.
524
525 \sa setCacheLimit(), insert()
526
527 \since 4.6
528*/
529#endif // QT_DEPRECATED_SINCE(6, 6)
530
531/*!
532 Returns the cache limit (in kilobytes).
533
534 The default cache limit is 10240 KB.
535
536 \sa setCacheLimit()
537*/
538
539int QPixmapCache::cacheLimit()
540{
541 if (!qt_pixmapcache_thread_test())
542 return 0;
543 return pm_cache()->maxCost();
544}
545
546/*!
547 Sets the cache limit to \a n kilobytes.
548
549 The default setting is 10240 KB.
550
551 \sa cacheLimit()
552*/
553
554void QPixmapCache::setCacheLimit(int n)
555{
556 if (!qt_pixmapcache_thread_test())
557 return;
558 pm_cache()->setMaxCost(n);
559}
560
561/*!
562 Removes the pixmap associated with \a key from the cache.
563*/
564void QPixmapCache::remove(const QString &key)
565{
566 if (key.isEmpty() || !qt_pixmapcache_thread_test())
567 return;
568 pm_cache()->remove(key);
569}
570
571/*!
572 Removes the pixmap associated with \a key from the cache and releases
573 the key for a future insertion.
574
575 \since 4.6
576*/
577void QPixmapCache::remove(const Key &key)
578{
579 if (!qt_pixmapcache_thread_test())
580 return;
581 //The key is not valid anymore, a flush happened before probably
582 if (!key.d || !key.d->isValid)
583 return;
584 pm_cache()->remove(key);
585}
586
587/*!
588 Removes all pixmaps from the cache.
589*/
590
591void QPixmapCache::clear()
592{
593 if (!QCoreApplication::closingDown() && !qt_pixmapcache_thread_test())
594 return;
595 QT_TRY {
596 if (pm_cache.exists())
597 pm_cache->clear();
598 } QT_CATCH(const std::bad_alloc &) {
599 // if we ran out of memory during pm_cache(), it's no leak,
600 // so just ignore it.
601 }
602}
603
604Q_AUTOTEST_EXPORT void qt_qpixmapcache_flush_detached_pixmaps() // for tst_qpixmapcache
605{
606 if (!qt_pixmapcache_thread_test())
607 return;
608 pm_cache()->flushDetachedPixmaps(nt: true);
609}
610
611Q_AUTOTEST_EXPORT int qt_qpixmapcache_qpixmapcache_total_used() // for tst_qpixmapcache
612{
613 if (!qt_pixmapcache_thread_test())
614 return 0;
615 return (pm_cache()->totalCost()+1023) / 1024;
616}
617
618/*!
619 \fn QPixmapCache::KeyData::KeyData()
620
621 \internal
622*/
623/*!
624 \fn QPixmapCache::KeyData::KeyData(const KeyData &other)
625 \internal
626*/
627/*!
628 \fn QPixmapCache::KeyData::~KeyData()
629
630 \internal
631*/
632QT_END_NAMESPACE
633

source code of qtbase/src/gui/image/qpixmapcache.cpp