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

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