aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/util/qquickpixmapcache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/util/qquickpixmapcache.cpp')
-rw-r--r--src/quick/util/qquickpixmapcache.cpp741
1 files changed, 465 insertions, 276 deletions
diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp
index e5c19edab0..10cc407c21 100644
--- a/src/quick/util/qquickpixmapcache.cpp
+++ b/src/quick/util/qquickpixmapcache.cpp
@@ -5,6 +5,7 @@
#include <QtQuick/private/qquickimageprovider_p.h>
#include <QtQuick/private/qquickprofiler_p.h>
#include <QtQuick/private/qsgcontext_p.h>
+#include <QtQuick/private/qsgrenderer_p.h>
#include <QtQuick/private/qsgtexturereader_p.h>
#include <QtQuick/qquickwindow.h>
@@ -27,6 +28,7 @@
#include <QtCore/qbuffer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qmetaobject.h>
+#include <QtCore/qscopeguard.h>
#if QT_CONFIG(qml_network)
#include <QtQml/qqmlnetworkaccessmanagerfactory.h>
@@ -34,9 +36,14 @@
#include <QtNetwork/qsslerror.h>
#endif
+#include <private/qdebug_p.h>
+
#define IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT 8
-#define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
+
+// After QQuickPixmapCache::unreferencePixmap() it may get deleted via a timer in 30 seconds
#define CACHE_EXPIRE_TIME 30
+
+// How many (1/4) of the unreferenced pixmaps to delete in QQuickPixmapCache::timerEvent()
#define CACHE_REMOVAL_FRACTION 4
#define PIXMAP_PROFILE(Code) Q_QUICK_PROFILE(QQuickProfiler::ProfilePixmapCache, Code)
@@ -49,16 +56,55 @@
QT_BEGIN_NAMESPACE
-const QLatin1String QQuickPixmap::itemGrabberScheme = QLatin1String("itemgrabber");
+using namespace Qt::Literals::StringLiterals;
-Q_LOGGING_CATEGORY(lcImg, "qt.quick.image")
+Q_DECLARE_LOGGING_CATEGORY(lcQsgLeak)
+
+#if defined(QT_DEBUG) && QT_CONFIG(thread)
+class ThreadAffinityMarker
+{
+public:
+ ThreadAffinityMarker() { attachToCurrentThread(); }
+
+ void assertOnAssignedThread()
+ {
+ QMutexLocker locker(&m_mutex);
+ if (!m_assignedThread)
+ attachToCurrentThread();
+ Q_ASSERT_X(m_assignedThread == QThread::currentThreadId(), Q_FUNC_INFO,
+ "Running on a wrong thread!");
+ }
-#ifndef QT_NO_DEBUG
-static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
+ void detachFromCurrentThread()
+ {
+ QMutexLocker locker(&m_mutex);
+ m_assignedThread = nullptr;
+ }
+
+ void attachToCurrentThread() { m_assignedThread = QThread::currentThreadId(); }
+
+private:
+ Qt::HANDLE m_assignedThread;
+ QMutex m_mutex;
+};
+# define Q_THREAD_AFFINITY_MARKER(x) ThreadAffinityMarker x
+# define Q_ASSERT_CALLED_ON_VALID_THREAD(x) x.assertOnAssignedThread()
+# define Q_DETACH_THREAD_AFFINITY_MARKER(x) x.detachFromCurrentThread()
+#else
+# define Q_THREAD_AFFINITY_MARKER(x)
+# define Q_ASSERT_CALLED_ON_VALID_THREAD(x)
+# define Q_DETACH_THREAD_AFFINITY_MARKER(x)
#endif
-// The cache limit describes the maximum "junk" in the cache.
-static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
+const QLatin1String QQuickPixmap::itemGrabberScheme = QLatin1String("itemgrabber");
+
+Q_LOGGING_CATEGORY(lcImg, "qt.quick.image")
+
+/*! \internal
+ The maximum currently-unused image data that can be stored for potential
+ later reuse, in bytes. See QQuickPixmapCache::shrinkCache()
+*/
+static int cache_limit = 2048 * 1024;
static inline QString imageProviderId(const QUrl &url)
{
@@ -110,9 +156,9 @@ public:
bool loading;
QQuickImageProviderOptions providerOptions;
- int redirectCount;
class Event : public QEvent {
+ Q_EVENT_DISABLE_COPY(Event);
public:
Event(ReadError, const QString &, const QSize &, QQuickTextureFactory *factory);
~Event();
@@ -136,22 +182,37 @@ private:
Q_DISABLE_COPY(QQuickPixmapReply)
public:
- static int finishedIndex;
- static int downloadProgressIndex;
+ static int finishedMethodIndex;
+ static int downloadProgressMethodIndex;
};
-class QQuickPixmapReaderThreadObject : public QObject {
+/*! \internal
+ Serves as an endpoint for notifications on the connected reader's thread, thus enforcing
+ execution of their continuation on the thread. */
+class ReaderThreadExecutionEnforcer : public QObject
+{
Q_OBJECT
public:
- QQuickPixmapReaderThreadObject(QQuickPixmapReader *);
- void processJobs();
- bool event(QEvent *e) override;
+ enum Event {
+ ProcessJobs = QEvent::User,
+ };
+
+ ReaderThreadExecutionEnforcer(QQuickPixmapReader *reader);
+
+ /*! \internal
+ Forces the execution of processJobs() on the original reader on the thread it's running on.
+ */
+ void processJobsOnReaderThreadLater();
+
public slots:
void asyncResponseFinished(QQuickImageResponse *response);
+ void asyncResponseFinished();
private slots:
void networkRequestDone();
- void asyncResponseFinished();
private:
+ Q_DISABLE_COPY(ReaderThreadExecutionEnforcer)
+ bool event(QEvent *e) override;
+
QQuickPixmapReader *reader;
};
@@ -168,12 +229,14 @@ public:
static QQuickPixmapReader *instance(QQmlEngine *engine);
static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
+ void startJob(QQuickPixmapReply *job);
protected:
void run() override;
private:
- friend class QQuickPixmapReaderThreadObject;
+ Q_DISABLE_COPY(QQuickPixmapReader)
+ friend class ReaderThreadExecutionEnforcer;
void processJobs();
void processJob(QQuickPixmapReply *, const QUrl &, const QString &, QQuickImageProvider::ImageType, const QSharedPointer<QQuickImageProvider> &);
#if QT_CONFIG(qml_network)
@@ -182,14 +245,30 @@ private:
void asyncResponseFinished(QQuickImageResponse *);
QList<QQuickPixmapReply*> jobs;
- QList<QQuickPixmapReply*> cancelled;
+ QList<QQuickPixmapReply *> cancelledJobs;
QQmlEngine *engine;
- QObject *eventLoopQuitHack;
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
+ /*! \internal
+ Returns a pointer to the thread object owned by the run loop in QQuickPixmapReader::run.
+ */
+ ReaderThreadExecutionEnforcer *readerThreadExecutionEnforcer()
+ {
+ return runLoopReaderThreadExecutionEnforcer;
+ }
+ QObject *eventLoopQuitHack;
QMutex mutex;
-
- // Owned not by the QQuickPixmapReader, but by the run() function.
- QQuickPixmapReaderThreadObject *threadObject = nullptr;
+ ReaderThreadExecutionEnforcer *runLoopReaderThreadExecutionEnforcer = nullptr;
+#else
+ /*! \internal
+ Returns a pointer to the thread object owned by this instance.
+ */
+ ReaderThreadExecutionEnforcer *readerThreadExecutionEnforcer()
+ {
+ return ownedReaderThreadExecutionEnforcer.get();
+ }
+ std::unique_ptr<ReaderThreadExecutionEnforcer> ownedReaderThreadExecutionEnforcer;
+#endif
#if QT_CONFIG(qml_network)
QNetworkAccessManager *networkAccessManager();
@@ -198,20 +277,33 @@ private:
#endif
QHash<QQuickImageResponse*,QQuickPixmapReply*> asyncResponses;
- static int replyDownloadProgress;
- static int replyFinished;
- static int downloadProgress;
- static int threadNetworkRequestDone;
+ Q_THREAD_AFFINITY_MARKER(m_creatorThreadAffinityMarker);
+ Q_THREAD_AFFINITY_MARKER(m_readerThreadAffinityMarker);
+
+ static int replyDownloadProgressMethodIndex;
+ static int replyFinishedMethodIndex;
+ static int downloadProgressMethodIndex;
+ static int threadNetworkRequestDoneMethodIndex;
static QHash<QQmlEngine *,QQuickPixmapReader*> readers;
public:
static QMutex readerMutex;
};
-class QQuickPixmapStore;
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
+# define PIXMAP_READER_LOCK() QMutexLocker locker(&mutex)
+#else
+# define PIXMAP_READER_LOCK()
+#endif
+
+class QQuickPixmapCache;
+
+/*! \internal
+ The private storage for QQuickPixmap.
+*/
class QQuickPixmapData
{
public:
- QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &rs,
+ QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &rs,
const QQuickImageProviderOptions &po, const QString &e)
: refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Error),
url(u), errorString(e), requestRegion(r), requestSize(rs),
@@ -222,10 +314,9 @@ public:
, storeToCache(true)
#endif
{
- declarativePixmaps.insert(pixmap);
}
- QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
+ QQuickPixmapData(const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
: refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Loading),
url(u), requestRegion(r), requestSize(s),
@@ -236,10 +327,9 @@ public:
, storeToCache(true)
#endif
{
- declarativePixmaps.insert(pixmap);
}
- QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture,
+ QQuickPixmapData(const QUrl &u, QQuickTextureFactory *texture,
const QSize &s, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po,
QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
: refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Ready),
@@ -251,10 +341,9 @@ public:
, storeToCache(true)
#endif
{
- declarativePixmaps.insert(pixmap);
}
- QQuickPixmapData(QQuickPixmap *pixmap, QQuickTextureFactory *texture)
+ QQuickPixmapData(QQuickTextureFactory *texture)
: refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Ready),
appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
@@ -265,24 +354,18 @@ public:
{
if (texture)
requestSize = implicitSize = texture->textureSize();
- declarativePixmaps.insert(pixmap);
}
~QQuickPixmapData()
{
- while (!declarativePixmaps.isEmpty()) {
- QQuickPixmap *referencer = declarativePixmaps.first();
- declarativePixmaps.remove(referencer);
- referencer->d = nullptr;
- }
delete textureFactory;
}
int cost() const;
void addref();
- void release(QQuickPixmapStore *store = nullptr);
+ void release(QQuickPixmapCache *store = nullptr);
void addToCache();
- void removeFromCache(QQuickPixmapStore *store = nullptr);
+ void removeFromCache(QQuickPixmapCache *store = nullptr);
uint refCount;
int frameCount;
@@ -301,11 +384,14 @@ public:
QColorSpace targetColorSpace;
QIODevice *specialDevice = nullptr;
+
+ // actual image data, after loading
QQuickTextureFactory *textureFactory;
- QIntrusiveList<QQuickPixmap, &QQuickPixmap::dataListNode> declarativePixmaps;
QQuickPixmapReply *reply;
+ // prev/next pointers to form a linked list for dereferencing pixmaps that are currently unused
+ // (those get lazily deleted in QQuickPixmapCache::shrinkCache())
QQuickPixmapData *prevUnreferenced;
QQuickPixmapData**prevUnreferencedPtr;
QQuickPixmapData *nextUnreferenced;
@@ -313,20 +399,22 @@ public:
#ifdef Q_OS_WEBOS
bool storeToCache;
#endif
+
+private:
+ Q_DISABLE_COPY(QQuickPixmapData)
};
-int QQuickPixmapReply::finishedIndex = -1;
-int QQuickPixmapReply::downloadProgressIndex = -1;
+int QQuickPixmapReply::finishedMethodIndex = -1;
+int QQuickPixmapReply::downloadProgressMethodIndex = -1;
// XXX
QHash<QQmlEngine *,QQuickPixmapReader*> QQuickPixmapReader::readers;
QMutex QQuickPixmapReader::readerMutex;
-int QQuickPixmapReader::replyDownloadProgress = -1;
-int QQuickPixmapReader::replyFinished = -1;
-int QQuickPixmapReader::downloadProgress = -1;
-int QQuickPixmapReader::threadNetworkRequestDone = -1;
-
+int QQuickPixmapReader::replyDownloadProgressMethodIndex = -1;
+int QQuickPixmapReader::replyFinishedMethodIndex = -1;
+int QQuickPixmapReader::downloadProgressMethodIndex = -1;
+int QQuickPixmapReader::threadNetworkRequestDoneMethodIndex = -1;
void QQuickPixmapReply::postReply(ReadError error, const QString &errorString,
const QSize &implicitSize, QQuickTextureFactory *factory)
@@ -349,8 +437,9 @@ QQuickPixmapReply::Event::~Event()
QNetworkAccessManager *QQuickPixmapReader::networkAccessManager()
{
if (!accessManager) {
- Q_ASSERT(threadObject);
- accessManager = QQmlEnginePrivate::get(engine)->createNetworkAccessManager(threadObject);
+ Q_ASSERT(readerThreadExecutionEnforcer());
+ accessManager = QQmlEnginePrivate::get(engine)->createNetworkAccessManager(
+ readerThreadExecutionEnforcer());
}
return accessManager;
}
@@ -463,6 +552,8 @@ public:
}
bool hasOpenGL;
QStringList fileSuffixes;
+private:
+ Q_DISABLE_COPY(BackendSupport)
};
Q_GLOBAL_STATIC(BackendSupport, backendSupport);
@@ -489,10 +580,11 @@ QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
, accessManager(nullptr)
#endif
{
+ Q_DETACH_THREAD_AFFINITY_MARKER(m_readerThreadAffinityMarker);
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
eventLoopQuitHack = new QObject;
eventLoopQuitHack->moveToThread(this);
- connect(eventLoopQuitHack, SIGNAL(destroyed(QObject *)), SLOT(quit()), Qt::DirectConnection);
-#if USE_THREADED_DOWNLOAD
+ QObject::connect(eventLoopQuitHack, &QObject::destroyed, this, &QThread::quit, Qt::DirectConnection);
start(QThread::LowestPriority);
#else
run(); // Call nonblocking run for ourselves.
@@ -501,38 +593,54 @@ QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
QQuickPixmapReader::~QQuickPixmapReader()
{
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_creatorThreadAffinityMarker);
+
readerMutex.lock();
readers.remove(engine);
readerMutex.unlock();
- mutex.lock();
- // manually cancel all outstanding jobs.
- for (QQuickPixmapReply *reply : std::as_const(jobs)) {
- if (reply->data && reply->data->reply == reply)
- reply->data->reply = nullptr;
- delete reply;
- }
- jobs.clear();
-#if QT_CONFIG(qml_network)
-
- const auto cancelJob = [this](QQuickPixmapReply *reply) {
- if (reply->loading) {
- cancelled.append(reply);
- reply->data = nullptr;
+ {
+ PIXMAP_READER_LOCK();
+ // manually cancel all outstanding jobs.
+ for (QQuickPixmapReply *reply : std::as_const(jobs)) {
+ if (reply->data && reply->data->reply == reply)
+ reply->data->reply = nullptr;
+ delete reply;
}
- };
+ jobs.clear();
+#if QT_CONFIG(qml_network)
+ const auto cancelJob = [this](QQuickPixmapReply *reply) {
+ if (reply->loading) {
+ cancelledJobs.append(reply);
+ reply->data = nullptr;
+ }
+ };
- for (auto *reply : std::as_const(networkJobs))
- cancelJob(reply);
+ for (auto *reply : std::as_const(networkJobs))
+ cancelJob(reply);
- for (auto *reply : std::as_const(asyncResponses))
- cancelJob(reply);
+ for (auto *reply : std::as_const(asyncResponses))
+ cancelJob(reply);
+#endif
+#if !QT_CONFIG(quick_pixmap_cache_threaded_download)
+ // In this case we won't be waiting, but we are on the correct thread already, so we can
+ // perform housekeeping synchronously now.
+ processJobs();
+#else // QT_CONFIG(quick_pixmap_cache_threaded_download) is true
+ // Perform housekeeping on all the jobs cancelled above soon...
+ if (readerThreadExecutionEnforcer())
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
#endif
- if (threadObject) threadObject->processJobs();
- mutex.unlock();
+ }
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
+ // ... schedule stopping of this thread via the eventLoopQuitHack (processJobs scheduled above
+ // will run first) ...
eventLoopQuitHack->deleteLater();
+ // ... and wait() for it all to finish, as the thread will only quit after eventLoopQuitHack
+ // has been deleted.
wait();
+#endif
#if QT_CONFIG(qml_network)
// While we've been waiting, the other thread may have added
@@ -558,28 +666,11 @@ QQuickPixmapReader::~QQuickPixmapReader()
#if QT_CONFIG(qml_network)
void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
{
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+
QQuickPixmapReply *job = networkJobs.take(reply);
if (job) {
- job->redirectCount++;
- if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
- QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
- if (redirect.isValid()) {
- QUrl url = reply->url().resolved(redirect.toUrl());
- QNetworkRequest req(url);
- req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
-
- reply->deleteLater();
- reply = networkAccessManager()->get(req);
-
- QMetaObject::connect(reply, replyDownloadProgress, job, downloadProgress);
- QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
-
- networkJobs.insert(reply, job);
- return;
- }
- }
-
QImage image;
QQuickPixmapReply::ReadError error = QQuickPixmapReply::NoError;
QString errorString;
@@ -614,20 +705,22 @@ void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
// send completion event to the QQuickPixmapReply
if (!factory)
factory = QQuickTextureFactory::textureFactoryForImage(image);
- mutex.lock();
- if (!cancelled.contains(job))
+
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(job))
job->postReply(error, errorString, readSize, factory);
- mutex.unlock();
}
reply->deleteLater();
- // kick off event loop again incase we have dropped below max request count
- threadObject->processJobs();
+ // kick off event loop again in case we have dropped below max request count
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
}
#endif // qml_network
void QQuickPixmapReader::asyncResponseFinished(QQuickImageResponse *response)
{
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+
QQuickPixmapReply *job = asyncResponses.take(response);
if (job) {
@@ -639,41 +732,40 @@ void QQuickPixmapReader::asyncResponseFinished(QQuickImageResponse *response)
errorString = response->errorString();
} else {
t = response->textureFactory();
- }
- mutex.lock();
- if (!cancelled.contains(job))
+ }
+
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(job))
job->postReply(error, errorString, t ? t->textureSize() : QSize(), t);
else
delete t;
- mutex.unlock();
}
response->deleteLater();
- // kick off event loop again incase we have dropped below max request count
- threadObject->processJobs();
+ // kick off event loop again in case we have dropped below max request count
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
}
-QQuickPixmapReaderThreadObject::QQuickPixmapReaderThreadObject(QQuickPixmapReader *i)
-: reader(i)
-{
-}
+ReaderThreadExecutionEnforcer::ReaderThreadExecutionEnforcer(QQuickPixmapReader *i) : reader(i) { }
-void QQuickPixmapReaderThreadObject::processJobs()
+void ReaderThreadExecutionEnforcer::processJobsOnReaderThreadLater()
{
- QCoreApplication::postEvent(this, new QEvent(QEvent::User));
+ QCoreApplication::postEvent(
+ this, new QEvent(QEvent::Type(ReaderThreadExecutionEnforcer::ProcessJobs)));
}
-bool QQuickPixmapReaderThreadObject::event(QEvent *e)
+bool ReaderThreadExecutionEnforcer::event(QEvent *e)
{
- if (e->type() == QEvent::User) {
+ switch (e->type()) {
+ case QEvent::Type(ReaderThreadExecutionEnforcer::ProcessJobs):
reader->processJobs();
return true;
- } else {
+ default:
return QObject::event(e);
}
}
-void QQuickPixmapReaderThreadObject::networkRequestDone()
+void ReaderThreadExecutionEnforcer::networkRequestDone()
{
#if QT_CONFIG(qml_network)
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
@@ -681,12 +773,12 @@ void QQuickPixmapReaderThreadObject::networkRequestDone()
#endif
}
-void QQuickPixmapReaderThreadObject::asyncResponseFinished(QQuickImageResponse *response)
+void ReaderThreadExecutionEnforcer::asyncResponseFinished(QQuickImageResponse *response)
{
reader->asyncResponseFinished(response);
}
-void QQuickPixmapReaderThreadObject::asyncResponseFinished()
+void ReaderThreadExecutionEnforcer::asyncResponseFinished()
{
QQuickImageResponse *response = static_cast<QQuickImageResponse *>(sender());
asyncResponseFinished(response);
@@ -694,16 +786,17 @@ void QQuickPixmapReaderThreadObject::asyncResponseFinished()
void QQuickPixmapReader::processJobs()
{
- QMutexLocker locker(&mutex);
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+ PIXMAP_READER_LOCK();
while (true) {
- if (cancelled.isEmpty() && jobs.isEmpty())
+ if (cancelledJobs.isEmpty() && jobs.isEmpty())
return; // Nothing else to do
// Clean cancelled jobs
- if (!cancelled.isEmpty()) {
- for (int i = 0; i < cancelled.size(); ++i) {
- QQuickPixmapReply *job = cancelled.at(i);
+ if (!cancelledJobs.isEmpty()) {
+ for (int i = 0; i < cancelledJobs.size(); ++i) {
+ QQuickPixmapReply *job = cancelledJobs.at(i);
#if QT_CONFIG(qml_network)
QNetworkReply *reply = networkJobs.key(job, 0);
if (reply) {
@@ -724,7 +817,7 @@ void QQuickPixmapReader::processJobs()
// deleteLater, since not owned by this thread
job->deleteLater();
}
- cancelled.clear();
+ cancelledJobs.clear();
}
if (!jobs.isEmpty()) {
@@ -753,7 +846,6 @@ void QQuickPixmapReader::processJobs()
;
}
-
if (usableJob) {
jobs.removeAt(i);
@@ -761,9 +853,13 @@ void QQuickPixmapReader::processJobs()
PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
locker.unlock();
+ auto relockMutexGuard = qScopeGuard(([&locker]() {
+ locker.relock();
+ }));
+#endif
processJob(job, url, localFile, imageType, provider);
- locker.relock();
}
}
@@ -776,6 +872,8 @@ void QQuickPixmapReader::processJobs()
void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &url, const QString &localFile,
QQuickImageProvider::ImageType imageType, const QSharedPointer<QQuickImageProvider> &provider)
{
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+
// fetch
if (url.scheme() == QLatin1String("image")) {
// Use QQuickImageProvider
@@ -783,10 +881,9 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
if (imageType == QQuickImageProvider::Invalid) {
QString errorStr = QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString());
- mutex.lock();
- if (!cancelled.contains(runningJob))
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob))
runningJob->postReply(QQuickPixmapReply::Loading, errorStr, readSize, nullptr);
- mutex.unlock();
return;
}
@@ -814,10 +911,11 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorCode = QQuickPixmapReply::Loading;
errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
- runningJob->postReply(errorCode, errorStr, readSize, QQuickTextureFactory::textureFactoryForImage(image));
- mutex.unlock();
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob)) {
+ runningJob->postReply(errorCode, errorStr, readSize,
+ QQuickTextureFactory::textureFactoryForImage(image));
+ }
break;
}
@@ -835,10 +933,13 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorCode = QQuickPixmapReply::Loading;
errorStr = QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString());
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
- runningJob->postReply(errorCode, errorStr, readSize, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()));
- mutex.unlock();
+
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob)) {
+ runningJob->postReply(
+ errorCode, errorStr, readSize,
+ QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()));
+ }
break;
}
@@ -856,12 +957,11 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorCode = QQuickPixmapReply::Loading;
errorStr = QQuickPixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob))
runningJob->postReply(errorCode, errorStr, readSize, t);
else
delete t;
- mutex.unlock();
break;
}
@@ -876,7 +976,8 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
}
{
- QObject::connect(response, SIGNAL(finished()), threadObject, SLOT(asyncResponseFinished()));
+ QObject::connect(response, &QQuickImageResponse::finished, readerThreadExecutionEnforcer(),
+ qOverload<>(&ReaderThreadExecutionEnforcer::asyncResponseFinished));
// as the response object can outlive the provider QSharedPointer, we have to extend the pointee's lifetime by that of the response
// we do this by capturing a copy of the QSharedPointer in a lambda, and dropping it once the lambda has been called
auto provider_copy = provider; // capturing provider would capture it as a const reference, and copy capture with initializer is only available in C++14
@@ -889,8 +990,9 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
//
// loadAcquire() synchronizes-with storeRelease() in QQuickImageResponsePrivate::_q_finished():
if (static_cast<QQuickImageResponsePrivate*>(QObjectPrivate::get(response))->finished.loadAcquire()) {
- QMetaObject::invokeMethod(threadObject, "asyncResponseFinished",
- Qt::QueuedConnection, Q_ARG(QQuickImageResponse*, response));
+ QMetaObject::invokeMethod(readerThreadExecutionEnforcer(), "asyncResponseFinished",
+ Qt::QueuedConnection,
+ Q_ARG(QQuickImageResponse *, response));
}
asyncResponses.insert(response, runningJob);
@@ -929,10 +1031,9 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
errorCode = QQuickPixmapReply::Decoding;
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob))
runningJob->postReply(errorCode, errorStr, readSize, factory);
- mutex.unlock();
return;
} else {
int frameCount;
@@ -952,10 +1053,11 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
errorCode = QQuickPixmapReply::Loading;
}
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
- runningJob->postReply(errorCode, errorStr, readSize, QQuickTextureFactory::textureFactoryForImage(image));
- mutex.unlock();
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob)) {
+ runningJob->postReply(errorCode, errorStr, readSize,
+ QQuickTextureFactory::textureFactoryForImage(image));
+ }
} else {
#if QT_CONFIG(qml_network)
// Network resource
@@ -963,8 +1065,10 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
QNetworkReply *reply = networkAccessManager()->get(req);
- QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress);
- QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
+ QMetaObject::connect(reply, replyDownloadProgressMethodIndex, runningJob,
+ downloadProgressMethodIndex);
+ QMetaObject::connect(reply, replyFinishedMethodIndex, readerThreadExecutionEnforcer(),
+ threadNetworkRequestDoneMethodIndex);
networkJobs.insert(reply, runningJob);
#else
@@ -994,24 +1098,28 @@ QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
{
- mutex.lock();
QQuickPixmapReply *reply = new QQuickPixmapReply(data);
reply->engineForReader = engine;
- jobs.append(reply);
- // XXX
- if (threadObject) threadObject->processJobs();
- mutex.unlock();
return reply;
}
+void QQuickPixmapReader::startJob(QQuickPixmapReply *job)
+{
+ PIXMAP_READER_LOCK();
+ jobs.append(job);
+ if (readerThreadExecutionEnforcer())
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
+}
+
void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
{
- mutex.lock();
+ PIXMAP_READER_LOCK();
if (reply->loading) {
- cancelled.append(reply);
+ cancelledJobs.append(reply);
reply->data = nullptr;
// XXX
- if (threadObject) threadObject->processJobs();
+ if (readerThreadExecutionEnforcer())
+ readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
} else {
// If loading was started (reply removed from jobs) but the reply was never processed
// (otherwise it would have deleted itself) we need to profile an error.
@@ -1020,50 +1128,46 @@ void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
}
delete reply;
}
- mutex.unlock();
}
void QQuickPixmapReader::run()
{
- if (replyDownloadProgress == -1) {
+ Q_ASSERT_CALLED_ON_VALID_THREAD(m_readerThreadAffinityMarker);
+
+ if (replyDownloadProgressMethodIndex == -1) {
#if QT_CONFIG(qml_network)
- replyDownloadProgress = QMetaMethod::fromSignal(&QNetworkReply::downloadProgress).methodIndex();
- replyFinished = QMetaMethod::fromSignal(&QNetworkReply::finished).methodIndex();
- const QMetaObject *ir = &QQuickPixmapReaderThreadObject::staticMetaObject;
- threadNetworkRequestDone = ir->indexOfSlot("networkRequestDone()");
+ replyDownloadProgressMethodIndex =
+ QMetaMethod::fromSignal(&QNetworkReply::downloadProgress).methodIndex();
+ replyFinishedMethodIndex = QMetaMethod::fromSignal(&QNetworkReply::finished).methodIndex();
+ const QMetaObject *ir = &ReaderThreadExecutionEnforcer::staticMetaObject;
+ threadNetworkRequestDoneMethodIndex = ir->indexOfSlot("networkRequestDone()");
#endif
- downloadProgress = QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
+ downloadProgressMethodIndex =
+ QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
}
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
const auto guard = qScopeGuard([this]() {
- // We need to delete the threadObject from the same thread.
- QMutexLocker lock(&mutex);
- delete threadObject;
- threadObject = nullptr;
+ // We need to delete the runLoopReaderThreadExecutionEnforcer from the same thread.
+ PIXMAP_READER_LOCK();
+ delete runLoopReaderThreadExecutionEnforcer;
+ runLoopReaderThreadExecutionEnforcer = nullptr;
});
{
- QMutexLocker lock(&mutex);
- Q_ASSERT(!threadObject);
- threadObject = new QQuickPixmapReaderThreadObject(this);
+ PIXMAP_READER_LOCK();
+ Q_ASSERT(!runLoopReaderThreadExecutionEnforcer);
+ runLoopReaderThreadExecutionEnforcer = new ReaderThreadExecutionEnforcer(this);
}
processJobs();
-#if USE_THREADED_DOWNLOAD
exec();
+#else
+ ownedReaderThreadExecutionEnforcer = std::make_unique<ReaderThreadExecutionEnforcer>(this);
+ processJobs();
#endif
}
-class QQuickPixmapKey
-{
-public:
- const QUrl *url;
- const QRect *region;
- const QSize *size;
- int frame;
- QQuickImageProviderOptions options;
-};
-
inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
{
return *lhs.url == *rhs.url &&
@@ -1078,62 +1182,70 @@ inline size_t qHash(const QQuickPixmapKey &key, size_t seed) noexcept
return qHashMulti(seed, *key.url, *key.region, *key.size, key.frame, key.options.autoTransform());
}
-class QQuickPixmapStore : public QObject
+#ifndef QT_NO_DEBUG_STREAM
+inline QDebug operator<<(QDebug debug, const QQuickPixmapKey &key)
{
- Q_OBJECT
-public:
- QQuickPixmapStore();
- ~QQuickPixmapStore();
-
- void unreferencePixmap(QQuickPixmapData *);
- void referencePixmap(QQuickPixmapData *);
-
- void purgeCache();
-
-protected:
- void timerEvent(QTimerEvent *) override;
-
-public:
- QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
-
-private:
- void shrinkCache(int remove);
-
- QQuickPixmapData *m_unreferencedPixmaps;
- QQuickPixmapData *m_lastUnreferencedPixmap;
+ QDebugStateSaver saver(debug);
+ debug.nospace();
+ if (!key.url) {
+ debug << "QQuickPixmapKey(0)";
+ return debug;
+ }
- int m_unreferencedCost;
- int m_timerId;
- bool m_destroying;
-};
-Q_GLOBAL_STATIC(QQuickPixmapStore, pixmapStore);
+ debug << "QQuickPixmapKey(" << key.url->toString() << " frame=" << key.frame;
+ if (!key.region->isEmpty()) {
+ debug << " region=";
+ QtDebugUtils::formatQRect(debug, *key.region);
+ }
+ if (!key.size->isEmpty()) {
+ debug << " size=";
+ QtDebugUtils::formatQSize(debug, *key.size);
+ }
+ debug << ')';
+ return debug;
+}
+#endif
+QQuickPixmapCache *QQuickPixmapCache::instance()
+{
+ static QQuickPixmapCache self;
+ return &self;
+}
-QQuickPixmapStore::QQuickPixmapStore()
- : m_unreferencedPixmaps(nullptr), m_lastUnreferencedPixmap(nullptr), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
+QQuickPixmapCache::~QQuickPixmapCache()
{
+ destroyCache();
}
-QQuickPixmapStore::~QQuickPixmapStore()
+/*! \internal
+ Empty the cache completely, to prevent leaks. Returns the number of
+ leaked pixmaps (should always be \c 0).
+
+ This is work the destructor needs to do, but we put it into a function
+ only to make it testable in autotests, because the static instance()
+ cannot be destroyed before shutdown.
+*/
+int QQuickPixmapCache::destroyCache()
{
+ if (m_destroying)
+ return -1;
+
m_destroying = true;
-#ifndef QT_NO_DEBUG
- int leakedPixmaps = 0;
-#endif
// Prevent unreferencePixmap() from assuming it needs to kick
// off the cache expiry timer, as we're shrinking the cache
// manually below after releasing all the pixmaps.
m_timerId = -2;
// unreference all (leaked) pixmaps
+ int leakedPixmaps = 0;
const auto cache = m_cache; // NOTE: intentional copy (QTBUG-65077); releasing items from the cache modifies m_cache.
for (auto *pixmap : cache) {
- int currRefCount = pixmap->refCount;
+ auto currRefCount = pixmap->refCount;
if (currRefCount) {
-#ifndef QT_NO_DEBUG
leakedPixmaps++;
-#endif
+ qCDebug(lcQsgLeak) << "leaked pixmap: refCount" << pixmap->refCount << pixmap->url << "frame" << pixmap->frame
+ << "size" << pixmap->requestSize << "region" << pixmap->requestRegion;
while (currRefCount > 0) {
pixmap->release(this);
currRefCount--;
@@ -1142,17 +1254,29 @@ QQuickPixmapStore::~QQuickPixmapStore()
}
// free all unreferenced pixmaps
- while (m_lastUnreferencedPixmap) {
+ while (m_lastUnreferencedPixmap)
shrinkCache(20);
- }
-#ifndef QT_NO_DEBUG
- if (leakedPixmaps && qsg_leak_check)
- qDebug("Number of leaked pixmaps: %i", leakedPixmaps);
-#endif
+ qCDebug(lcQsgLeak, "Number of leaked pixmaps: %i", leakedPixmaps);
+ return leakedPixmaps;
+}
+
+qsizetype QQuickPixmapCache::referencedCost() const
+{
+ qsizetype ret = 0;
+ QMutexLocker locker(&m_cacheMutex);
+ for (const auto *pixmap : std::as_const(m_cache)) {
+ if (pixmap->refCount)
+ ret += pixmap->cost();
+ }
+ return ret;
}
-void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
+/*! \internal
+ Declare that \a data is currently unused so that shrinkCache() can lazily
+ delete it later.
+*/
+void QQuickPixmapCache::unreferencePixmap(QQuickPixmapData *data)
{
Q_ASSERT(data->prevUnreferenced == nullptr);
Q_ASSERT(data->prevUnreferencedPtr == nullptr);
@@ -1160,8 +1284,10 @@ void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
data->nextUnreferenced = m_unreferencedPixmaps;
data->prevUnreferencedPtr = &m_unreferencedPixmaps;
- if (!m_destroying) // the texture factories may have been cleaned up already.
+ if (!m_destroying) { // the texture factories may have been cleaned up already.
m_unreferencedCost += data->cost();
+ qCDebug(lcImg) << data->url << "had cost" << data->cost() << "of total unreferenced" << m_unreferencedCost;
+ }
m_unreferencedPixmaps = data;
if (m_unreferencedPixmaps->nextUnreferenced) {
@@ -1180,7 +1306,11 @@ void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
}
}
-void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
+/*! \internal
+ Declare that \a data is being used (by a QQuickPixmap) so that
+ shrinkCache() won't delete it. (This is not reference counting though.)
+*/
+void QQuickPixmapCache::referencePixmap(QQuickPixmapData *data)
{
Q_ASSERT(data->prevUnreferencedPtr);
@@ -1197,10 +1327,16 @@ void QQuickPixmapStore::referencePixmap(QQuickPixmapData *data)
data->prevUnreferenced = nullptr;
m_unreferencedCost -= data->cost();
+ qCDebug(lcImg) << data->url << "subtracts cost" << data->cost() << "of total" << m_unreferencedCost;
}
-void QQuickPixmapStore::shrinkCache(int remove)
+/*! \internal
+ Delete the least-recently-released QQuickPixmapData instances
+ until the remaining bytes are less than cache_limit.
+*/
+void QQuickPixmapCache::shrinkCache(int remove)
{
+ qCDebug(lcImg) << "reduce unreferenced cost" << m_unreferencedCost << "to less than limit" << cache_limit;
while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
QQuickPixmapData *data = m_lastUnreferencedPixmap;
Q_ASSERT(data->nextUnreferenced == nullptr);
@@ -1219,7 +1355,7 @@ void QQuickPixmapStore::shrinkCache(int remove)
}
}
-void QQuickPixmapStore::timerEvent(QTimerEvent *)
+void QQuickPixmapCache::timerEvent(QTimerEvent *)
{
int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
@@ -1231,23 +1367,24 @@ void QQuickPixmapStore::timerEvent(QTimerEvent *)
}
}
-void QQuickPixmapStore::purgeCache()
+void QQuickPixmapCache::purgeCache()
{
shrinkCache(m_unreferencedCost);
}
void QQuickPixmap::purgeCache()
{
- pixmapStore()->purgeCache();
+ QQuickPixmapCache::instance()->purgeCache();
}
QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
: data(d), engineForReader(nullptr), requestRegion(d->requestRegion), requestSize(d->requestSize),
- url(d->url), loading(false), providerOptions(d->providerOptions), redirectCount(0)
+ url(d->url), loading(false), providerOptions(d->providerOptions)
{
- if (finishedIndex == -1) {
- finishedIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::finished).methodIndex();
- downloadProgressIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
+ if (finishedMethodIndex == -1) {
+ finishedMethodIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::finished).methodIndex();
+ downloadProgressMethodIndex =
+ QMetaMethod::fromSignal(&QQuickPixmapReply::downloadProgress).methodIndex();
}
}
@@ -1303,10 +1440,10 @@ void QQuickPixmapData::addref()
++refCount;
PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapReferenceCountChanged>(url, refCount));
if (prevUnreferencedPtr)
- pixmapStore()->referencePixmap(this);
+ QQuickPixmapCache::instance()->referencePixmap(this);
}
-void QQuickPixmapData::release(QQuickPixmapStore *store)
+void QQuickPixmapData::release(QQuickPixmapCache *store)
{
Q_ASSERT(refCount > 0);
--refCount;
@@ -1323,7 +1460,7 @@ void QQuickPixmapData::release(QQuickPixmapStore *store)
QQuickPixmapReader::readerMutex.unlock();
}
- store = store ? store : pixmapStore();
+ store = store ? store : QQuickPixmapCache::instance();
if (pixmapStatus == QQuickPixmap::Ready
#ifdef Q_OS_WEBOS
&& storeToCache
@@ -1340,32 +1477,52 @@ void QQuickPixmapData::release(QQuickPixmapStore *store)
}
}
+/*! \internal
+ Add this to the QQuickPixmapCache singleton.
+
+ \note The actual image will end up in QQuickPixmapData::textureFactory.
+ At the time addToCache() is called, it's generally not yet loaded; so the
+ qCDebug() below cannot say how much data we're committing to storing.
+ (On the other hand, removeFromCache() can tell.) QQuickTextureFactory is an
+ abstraction for image data. See QQuickDefaultTextureFactory for example:
+ it stores a QImage directly. Other QQuickTextureFactory subclasses store data
+ in other ways.
+*/
void QQuickPixmapData::addToCache()
{
if (!inCache) {
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
- pixmapStore()->m_cache.insert(key, this);
+ QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
+ if (lcImg().isDebugEnabled()) {
+ qCDebug(lcImg) << "adding" << key << "to total" << QQuickPixmapCache::instance()->m_cache.size();
+ for (auto it = QQuickPixmapCache::instance()->m_cache.keyBegin(); it != QQuickPixmapCache::instance()->m_cache.keyEnd(); ++it) {
+ if (*(it->url) == url && it->frame == frame)
+ qDebug(lcImg) << " similar pre-existing:" << *it;
+ }
+ }
+ QQuickPixmapCache::instance()->m_cache.insert(key, this);
inCache = true;
PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
- url, pixmapStore()->m_cache.size()));
+ url, QQuickPixmapCache::instance()->m_cache.size()));
}
}
-void QQuickPixmapData::removeFromCache(QQuickPixmapStore *store)
+void QQuickPixmapData::removeFromCache(QQuickPixmapCache *store)
{
-
if (inCache) {
if (!store)
- store = pixmapStore();
+ store = QQuickPixmapCache::instance();
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
+ QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
store->m_cache.remove(key);
+ qCDebug(lcImg) << "removed" << key << implicitSize << "; total remaining" << QQuickPixmapCache::instance()->m_cache.size();
inCache = false;
PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
url, store->m_cache.size()));
}
}
-static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url,
+static QQuickPixmapData* createPixmapDataSync(QQmlEngine *engine, const QUrl &url,
const QRect &requestRegion, const QSize &requestSize,
const QQuickImageProviderOptions &providerOptions, int frame, bool *ok,
qreal devicePixelRatio)
@@ -1383,7 +1540,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
switch (imageType) {
case QQuickImageProvider::Invalid:
- return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
+ return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
case QQuickImageProvider::Texture:
{
@@ -1391,7 +1548,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
: provider->requestTexture(imageId(url), &readSize, requestSize);
if (texture) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestRegion, requestSize,
+ return new QQuickPixmapData(url, texture, readSize, requestRegion, requestSize,
providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
}
break;
@@ -1403,7 +1560,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
: provider->requestImage(imageId(url), &readSize, requestSize);
if (!image.isNull()) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image),
+ return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(image),
readSize, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
}
@@ -1415,7 +1572,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
: provider->requestPixmap(imageId(url), &readSize, requestSize);
if (!pixmap.isNull()) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()),
+ return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()),
readSize, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
}
@@ -1429,7 +1586,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
}
// provider has bad image type, or provider returned null image
- return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
+ return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
}
@@ -1447,7 +1604,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
QQuickTextureFactory *factory = texReader.read();
if (factory) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, factory, factory->textureSize(), requestRegion, requestSize,
+ return new QQuickPixmapData(url, factory, factory->textureSize(), requestRegion, requestSize,
providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
} else {
errorString = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
@@ -1461,7 +1618,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
if (readImage(url, &f, &image, &errorString, &readSize, &frameCount, requestRegion, requestSize,
providerOptions, &appliedTransform, frame, devicePixelRatio)) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
+ return new QQuickPixmapData(url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
providerOptions, appliedTransform, frame, frameCount);
} else if (f.fileName() != localFile) {
errorString += QString::fromLatin1(" (%1)").arg(f.fileName());
@@ -1470,7 +1627,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
} else {
errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
}
- return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions, errorString);
+ return new QQuickPixmapData(url, requestRegion, requestSize, providerOptions, errorString);
}
@@ -1492,6 +1649,12 @@ QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
load(engine, url);
}
+QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, Options options)
+: d(nullptr)
+{
+ load(engine, url, options);
+}
+
QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &region, const QSize &size)
: d(nullptr)
{
@@ -1500,7 +1663,7 @@ QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &reg
QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
{
- d = new QQuickPixmapData(this, url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
+ d = new QQuickPixmapData(url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform);
d->addToCache();
}
@@ -1508,7 +1671,6 @@ QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
QQuickPixmap::~QQuickPixmap()
{
if (d) {
- d->declarativePixmaps.remove(this);
d->release();
d = nullptr;
}
@@ -1616,18 +1778,24 @@ void QQuickPixmap::setImage(const QImage &p)
{
clear();
- if (!p.isNull())
- d = new QQuickPixmapData(this, QQuickTextureFactory::textureFactoryForImage(p));
+ if (!p.isNull()) {
+ if (d)
+ d->release();
+ d = new QQuickPixmapData(QQuickTextureFactory::textureFactoryForImage(p));
+ }
}
void QQuickPixmap::setPixmap(const QQuickPixmap &other)
{
+ if (d == other.d)
+ return;
clear();
if (other.d) {
+ if (d)
+ d->release();
d = other.d;
d->addref();
- d->declarativePixmaps.insert(this);
}
}
@@ -1680,14 +1848,14 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
qreal devicePixelRatio)
{
if (d) {
- d->declarativePixmaps.remove(this);
d->release();
d = nullptr;
}
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
- QQuickPixmapStore *store = pixmapStore();
+ QQuickPixmapCache *store = QQuickPixmapCache::instance();
+ QMutexLocker locker(&QQuickPixmapCache::instance()->m_cacheMutex);
QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
#ifdef Q_OS_WEBOS
@@ -1711,6 +1879,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
iter = store->m_cache.find(key);
if (iter == store->m_cache.end()) {
+ locker.unlock();
if (url.scheme() == QLatin1String("image")) {
QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
if (auto provider = enginePrivate->imageProvider(imageProviderId(url)).staticCast<QQuickImageProvider>()) {
@@ -1727,7 +1896,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
if (!(options & QQuickPixmap::Asynchronous)) {
bool ok = false;
PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
- d = createPixmapDataSync(this, engine, url, requestRegion, requestSize, providerOptions, frame, &ok, devicePixelRatio);
+ d = createPixmapDataSync(engine, url, requestRegion, requestSize, providerOptions, frame, &ok, devicePixelRatio);
if (ok) {
PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height())));
if (options & QQuickPixmap::Cache)
@@ -1747,7 +1916,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
return;
- d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
+ d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
if (options & QQuickPixmap::Cache)
d->addToCache();
@@ -1756,12 +1925,14 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
#endif
QQuickPixmapReader::readerMutex.lock();
- d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
+ QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
+ d->reply = reader->getImage(d);
+ reader->startJob(d->reply);
QQuickPixmapReader::readerMutex.unlock();
} else {
d = *iter;
d->addref();
- d->declarativePixmaps.insert(this);
+ qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame;
}
}
@@ -1778,37 +1949,43 @@ void QQuickPixmap::loadImageFromDevice(QQmlEngine *engine, QIODevice *device, co
{
auto oldD = d;
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
- QQuickPixmapStore *store = pixmapStore();
+ QQuickPixmapCache *store = QQuickPixmapCache::instance();
QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
+ QMutexLocker locker(&store->m_cacheMutex);
iter = store->m_cache.find(key);
if (iter == store->m_cache.end()) {
if (!engine)
return;
- d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
+ locker.unlock();
+ d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
d->specialDevice = device;
d->addToCache();
QQuickPixmapReader::readerMutex.lock();
- d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
+ QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
+ d->reply = reader->getImage(d);
if (oldD) {
- QObject::connect(d->reply, &QQuickPixmapReply::finished, [oldD]() {
+ QObject::connect(d->reply, &QQuickPixmapReply::destroyed, store, [oldD]() {
oldD->release();
- });
+ }, Qt::QueuedConnection);
}
+ reader->startJob(d->reply);
QQuickPixmapReader::readerMutex.unlock();
} else {
d = *iter;
d->addref();
- d->declarativePixmaps.insert(this);
+ qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame << "refCount" << d->refCount;
+ locker.unlock();
+ if (oldD)
+ oldD->release();
}
}
void QQuickPixmap::clear()
{
if (d) {
- d->declarativePixmaps.remove(this);
d->release();
d = nullptr;
}
@@ -1819,7 +1996,6 @@ void QQuickPixmap::clear(QObject *obj)
if (d) {
if (d->reply)
QObject::disconnect(d->reply, nullptr, obj, nullptr);
- d->declarativePixmaps.remove(this);
d->release();
d = nullptr;
}
@@ -1829,11 +2005,22 @@ bool QQuickPixmap::isCached(const QUrl &url, const QRect &requestRegion, const Q
const int frame, const QQuickImageProviderOptions &options)
{
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, options };
- QQuickPixmapStore *store = pixmapStore();
+ QQuickPixmapCache *store = QQuickPixmapCache::instance();
return store->m_cache.contains(key);
}
+bool QQuickPixmap::isScalableImageFormat(const QUrl &url)
+{
+ if (url.scheme() == "image"_L1)
+ return true;
+
+ const QString stringUrl = url.path(QUrl::PrettyDecoded);
+ return stringUrl.endsWith("svg"_L1)
+ || stringUrl.endsWith("svgz"_L1)
+ || stringUrl.endsWith("pdf"_L1);
+}
+
bool QQuickPixmap::connectFinished(QObject *object, const char *method)
{
if (!d || !d->reply) {
@@ -1851,7 +2038,7 @@ bool QQuickPixmap::connectFinished(QObject *object, int method)
return false;
}
- return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedIndex, object, method);
+ return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedMethodIndex, object, method);
}
bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
@@ -1861,7 +2048,8 @@ bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
return false;
}
- return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
+ return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object,
+ method);
}
bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
@@ -1871,7 +2059,8 @@ bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
return false;
}
- return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method);
+ return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressMethodIndex, object,
+ method);
}
QColorSpace QQuickPixmap::colorSpace() const