Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qpixmapcache.cpp
Go to the documentation of this file.
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 "qbasictimer.h"
6#include "qobject.h"
7#include "qdebug.h"
9#include "qthread.h"
11
12using namespace std::chrono_literals;
13
15
16/*!
17 \class QPixmapCache
18 \inmodule QtGui
19
20 \brief The QPixmapCache class provides an application-wide cache for pixmaps.
21
22 This class is a tool for optimized drawing with QPixmap. You can
23 use it to store temporary pixmaps that are expensive to generate
24 without using more storage space than cacheLimit(). Use insert()
25 to insert pixmaps, find() to find them, and clear() to empty the
26 cache.
27
28 QPixmapCache contains no member data, only static functions to
29 access the global pixmap cache. It creates an internal QCache
30 object for caching the pixmaps.
31
32 The cache associates a pixmap with a user-provided string as a key,
33 or with a QPixmapCache::Key that the cache generates.
34 Using QPixmapCache::Key for keys is faster than using strings. The string API is
35 very convenient for complex keys but the QPixmapCache::Key API will be very
36 efficient and convenient for a one-to-one object-to-pixmap mapping - in
37 this case, you can store the keys as members of an object.
38
39 If two pixmaps are inserted into the cache using equal keys then the
40 last pixmap will replace the first pixmap in the cache. This follows the
41 behavior of the QHash and QCache classes.
42
43 The cache becomes full when the total size of all pixmaps in the
44 cache exceeds cacheLimit(). The initial cache limit is 10240 KB (10 MB);
45 you can change this by calling setCacheLimit() with the required value.
46 A pixmap takes roughly (\e{width} * \e{height} * \e{depth})/8 bytes of
47 memory.
48
49 The \e{Qt Quarterly} article
50 \l{http://doc.qt.io/archives/qq/qq12-qpixmapcache.html}{Optimizing
51 with QPixmapCache} explains how to use QPixmapCache to speed up
52 applications by caching the results of painting.
53
54 \note QPixmapCache is only usable from the application's main thread.
55 Access from other threads will be ignored and return failure.
56
57 \sa QCache, QPixmap
58*/
59
60static const int cache_limit_default = 10240; // 10 MB cache limit
61
62static inline qsizetype cost(const QPixmap &pixmap)
63{
64 // make sure to do a 64bit calculation; qsizetype might be smaller
65 const qint64 costKb = static_cast<qint64>(pixmap.width())
66 * pixmap.height() * pixmap.depth() / (8 * 1024);
67 const qint64 costMax = std::numeric_limits<qsizetype>::max();
68 // a small pixmap should have at least a cost of 1(kb)
69 return static_cast<qsizetype>(qBound(1LL, costKb, costMax));
70}
71
72static inline bool qt_pixmapcache_thread_test()
73{
74 if (Q_LIKELY(QThread::isMainThread()))
75 return true;
76
77 return false;
78}
79
80/*!
81 \class QPixmapCache::Key
82 \brief The QPixmapCache::Key class can be used for efficient access
83 to the QPixmapCache.
84 \inmodule QtGui
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 \since 5.6
149 \memberswap{key}
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
178{
180public:
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);
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 QBasicTimer timer;
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 \fn size_t QPixmapCache::Key::qHash(const Key &key, size_t seed)
223 \since 6.6
224 \qhash{QPixmapCache::Key}
225*/
226size_t QPixmapCache::Key::hash(size_t seed) const noexcept
227{
228 return qHash(this->d ? this->d->key : 0, seed);
229}
230
231QPMCache::QPMCache()
232 : QObject(nullptr),
233 QCache<QPixmapCache::Key, QPixmapCacheEntry>(cache_limit_default),
234 keyArray(nullptr), ps(0), keyArraySize(0), freeKey(0), t(false)
235{
236}
238{
239 clear();
240 free(keyArray);
241}
242
243/*
244 This is supposed to cut the cache size down by about 25% in a
245 minute once the application becomes idle, to let any inserted pixmap
246 remain in the cache for some time before it becomes a candidate for
247 cleaning-up, and to not cut down the size of the cache while the
248 cache is in active use.
249
250 When the last detached pixmap has been deleted from the cache, kill the
251 timer so Qt won't keep the CPU from going into sleep mode. Currently
252 the timer is not restarted when the pixmap becomes unused, but it does
253 restart once something else is added (i.e. the cache space is actually needed).
254
255 Returns \c true if any were removed.
256*/
258{
259 auto mc = maxCost();
260 const qsizetype currentTotal = totalCost();
261 const qsizetype oldSize = size();
262 if (currentTotal)
263 setMaxCost(nt ? currentTotal * 3 / 4 : currentTotal - 1);
264 setMaxCost(mc);
265 ps = totalCost();
266 return size() != oldSize;
267}
268
269void QPMCache::timerEvent(QTimerEvent *)
270{
271 bool nt = totalCost() == ps;
272 if (!flushDetachedPixmaps(nt)) {
273 timer.stop();
274 } else if (nt != t) {
275 timer.start(nt ? soon_time : flush_time, this);
276 t = nt;
277 }
278}
279
280
281QPixmap *QPMCache::object(const QString &key) const
282{
283 if (const auto it = cacheKeys.find(key); it != cacheKeys.cend())
284 return object(it.value());
285 return nullptr;
286}
287
288QPixmap *QPMCache::object(const QPixmapCache::Key &key) const
289{
290 Q_ASSERT(key.isValid());
291 QPixmap *ptr = QCache<QPixmapCache::Key, QPixmapCacheEntry>::object(key);
292 //We didn't find the pixmap in the cache, the key is not valid anymore
293 if (!ptr)
294 const_cast<QPMCache *>(this)->releaseKey(key);
295 return ptr;
296}
297
298bool QPMCache::insert(const QString& key, const QPixmap &pixmap, int cost)
299{
300 //If for the same key we add already a pixmap we should delete it
301 remove(key);
302
303 // this will create a new key; the old one has been removed
304 auto k = insert(pixmap, cost);
305 if (k.isValid()) {
306 k.d->stringKey = key;
307 cacheKeys[key] = std::move(k);
308 return true;
309 }
310 return false;
311}
312
313QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost)
314{
315 QPixmapCache::Key cacheKey = createKey(); // invalidated by ~QPixmapCacheEntry on failed insert
316 bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
317 Q_ASSERT(success || !cacheKey.isValid());
318 if (success) {
319 if (!timer.isActive()) {
320 timer.start(flush_time, this);
321 t = false;
322 }
323 }
324 return cacheKey;
325}
326
327bool QPMCache::remove(const QString &key)
328{
329 const auto cacheKey = cacheKeys.take(key);
330 return cacheKey.isValid() && remove(cacheKey);
331}
332
333bool QPMCache::remove(const QPixmapCache::Key &key)
334{
335 return QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
336}
337
339{
340 if (size <= keyArraySize || size == 0)
341 return;
342 keyArray = q_check_ptr(static_cast<int *>(realloc(keyArray,
343 size * sizeof(int))));
344 for (int i = keyArraySize; i != size; ++i)
345 keyArray[i] = i + 1;
346 keyArraySize = size;
347}
348
350{
351 if (freeKey == keyArraySize)
352 resizeKeyArray(keyArraySize ? keyArraySize << 1 : 2);
353 int id = freeKey;
354 freeKey = keyArray[id];
355 QPixmapCache::Key key;
356 QPixmapCache::KeyData *d = QPMCache::getKeyData(&key);
357 d->key = ++id;
358 return key;
359}
360
361void QPMCache::releaseKey(const QPixmapCache::Key &key)
362{
363 QPixmapCache::KeyData *keyData = key.d;
364 if (!keyData)
365 return;
366 if (!keyData->stringKey.isNull())
367 cacheKeys.remove(keyData->stringKey);
368 if (keyData->key > keyArraySize || keyData->key <= 0)
369 return;
370 keyData->key--;
371 keyArray[keyData->key] = freeKey;
372 freeKey = keyData->key;
373 keyData->isValid = false;
374 keyData->key = 0;
375}
376
378{
379 free(keyArray);
380 keyArray = nullptr;
381 freeKey = 0;
382 keyArraySize = 0;
383 //Mark all keys as invalid
384 const QList<QPixmapCache::Key> keys = QCache<QPixmapCache::Key, QPixmapCacheEntry>::keys();
385 for (const auto &key : keys) {
386 if (key.d)
387 key.d->isValid = false;
388 }
389 QCache<QPixmapCache::Key, QPixmapCacheEntry>::clear();
390 // Nothing left to flush; stop the timer
391 timer.stop();
392}
393
394QPixmapCache::KeyData* QPMCache::getKeyData(QPixmapCache::Key *key)
395{
396 if (!key->d)
397 key->d = new QPixmapCache::KeyData;
398 return key->d;
399}
400
401Q_GLOBAL_STATIC(QPMCache, pm_cache)
402
403int Q_AUTOTEST_EXPORT q_QPixmapCache_keyHashSize()
404{
405 return pm_cache()->size();
406}
407
409{
410 pm_cache()->releaseKey(key);
411}
412
413/*!
414 Looks for a cached pixmap associated with the given \a key in the cache.
415 If the pixmap is found, the function sets \a pixmap to that pixmap and
416 returns \c true; otherwise it leaves \a pixmap alone and returns \c false.
417
418 Example:
419 \snippet code/src_gui_image_qpixmapcache.cpp 1
420*/
421
422bool QPixmapCache::find(const QString &key, QPixmap *pixmap)
423{
424 if (key.isEmpty() || !qt_pixmapcache_thread_test())
425 return false;
426 QPixmap *ptr = pm_cache()->object(key);
427 if (ptr && pixmap)
428 *pixmap = *ptr;
429 return ptr != nullptr;
430}
431
432/*!
433 Looks for a cached pixmap associated with the given \a key in the cache.
434 If the pixmap is found, the function sets \a pixmap to that pixmap and
435 returns \c true; otherwise it leaves \a pixmap alone and returns \c false. If
436 the pixmap is not found, it means that the \a key is no longer valid,
437 so it will be released for the next insertion.
438*/
439bool QPixmapCache::find(const Key &key, QPixmap *pixmap)
440{
442 return false;
443 //The key is not valid anymore, a flush happened before probably
444 if (!key.d || !key.d->isValid)
445 return false;
446 QPixmap *ptr = pm_cache()->object(key);
447 if (ptr && pixmap)
448 *pixmap = *ptr;
449 return ptr != nullptr;
450}
451
452/*!
453 Inserts a copy of the pixmap \a pixmap associated with the \a key into
454 the cache.
455
456 All pixmaps inserted by the Qt library have a key starting with
457 "$qt", so your own pixmap keys should never begin "$qt".
458
459 When a pixmap is inserted and the cache is about to exceed its
460 limit, it removes pixmaps until there is enough room for the
461 pixmap to be inserted.
462
463 The oldest pixmaps (least recently accessed in the cache) are
464 deleted when more space is needed.
465
466 The function returns \c true if the object was inserted into the
467 cache; otherwise it returns \c false.
468
469 \sa setCacheLimit()
470*/
471
472bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap)
473{
474 if (key.isEmpty() || !qt_pixmapcache_thread_test())
475 return false;
476 return pm_cache()->insert(key, pixmap, cost(pixmap));
477}
478
479/*!
480 Inserts a copy of the given \a pixmap into the cache and returns a key
481 that can be used to retrieve it.
482
483 When a pixmap is inserted and the cache is about to exceed its
484 limit, it removes pixmaps until there is enough room for the
485 pixmap to be inserted.
486
487 The oldest pixmaps (least recently accessed in the cache) are
488 deleted when more space is needed.
489
490 \sa setCacheLimit()
491*/
492QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap)
493{
494 if (!qt_pixmapcache_thread_test())
495 return QPixmapCache::Key();
496 return pm_cache()->insert(pixmap, cost(pixmap));
497}
498
499#if QT_DEPRECATED_SINCE(6, 6)
500/*!
501 \fn bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap)
502
503 \deprecated [6.6] Use \c{remove(key); key = insert(pixmap);} instead.
504
505 Replaces the pixmap associated with the given \a key with the \a pixmap
506 specified. Returns \c true if the \a pixmap has been correctly inserted into
507 the cache; otherwise returns \c false.
508
509 The passed \a key is updated to reference \a pixmap now. Other copies of \a
510 key, if any, still refer to the old pixmap, which is, however, removed from
511 the cache by this function.
512
513 \sa setCacheLimit(), insert()
514*/
515#endif // QT_DEPRECATED_SINCE(6, 6)
516
517/*!
518 Returns the cache limit (in kilobytes).
519
520 The default cache limit is 10240 KB.
521
522 \sa setCacheLimit()
523*/
524
525int QPixmapCache::cacheLimit()
526{
528 return 0;
529 return pm_cache()->maxCost();
530}
531
532/*!
533 Sets the cache limit to \a n kilobytes.
534
535 The default setting is 10240 KB.
536
537 \sa cacheLimit()
538*/
539
540void QPixmapCache::setCacheLimit(int n)
541{
543 return;
544 pm_cache()->setMaxCost(n);
545}
546
547/*!
548 Removes the pixmap associated with \a key from the cache.
549*/
550void QPixmapCache::remove(const QString &key)
551{
552 if (key.isEmpty() || !qt_pixmapcache_thread_test())
553 return;
554 pm_cache()->remove(key);
555}
556
557/*!
558 Removes the pixmap associated with \a key from the cache and releases
559 the key for a future insertion.
560*/
561void QPixmapCache::remove(const Key &key)
562{
564 return;
565 //The key is not valid anymore, a flush happened before probably
566 if (!key.d || !key.d->isValid)
567 return;
568 pm_cache()->remove(key);
569}
570
571/*!
572 Removes all pixmaps from the cache.
573*/
574
575void QPixmapCache::clear()
576{
577 if (!QCoreApplication::closingDown() && !qt_pixmapcache_thread_test())
578 return;
579 QT_TRY {
580 if (pm_cache.exists())
581 pm_cache->clear();
582 } QT_CATCH(const std::bad_alloc &) {
583 // if we ran out of memory during pm_cache(), it's no leak,
584 // so just ignore it.
585 }
586}
587
589{
591 return;
592 pm_cache()->flushDetachedPixmaps(true);
593}
594
596{
598 return 0;
599 return (pm_cache()->totalCost()+1023) / 1024;
600}
601
602/*!
603 \fn QPixmapCache::KeyData::KeyData()
604
605 \internal
606*/
607/*!
608 \fn QPixmapCache::KeyData::KeyData(const KeyData &other)
609 \internal
610*/
611/*!
612 \fn QPixmapCache::KeyData::~KeyData()
613
614 \internal
615*/
616QT_END_NAMESPACE
bool remove(const QString &key)
bool insert(const QString &key, const QPixmap &pixmap, int cost)
void releaseKey(const QPixmapCache::Key &key)
static QPixmapCache::KeyData * getKeyData(QPixmapCache::Key *key)
bool flushDetachedPixmaps(bool nt)
QPixmap * object(const QString &key) const
static QPixmapCache::KeyData * get(const QPixmapCache::Key &key)
void resizeKeyArray(int size)
QPixmapCache::Key createKey()
QPixmapCache::Key insert(const QPixmap &pixmap, int cost)
void timerEvent(QTimerEvent *) override
This event handler can be reimplemented in a subclass to receive timer events for the object.
static const int cache_limit_default
static bool qt_pixmapcache_thread_test()
static qsizetype cost(const QPixmap &pixmap)
Q_AUTOTEST_EXPORT int qt_qpixmapcache_qpixmapcache_total_used()
Q_AUTOTEST_EXPORT void qt_qpixmapcache_flush_detached_pixmaps()