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.cpp971
1 files changed, 618 insertions, 353 deletions
diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp
index 1550e7e65d..10cc407c21 100644
--- a/src/quick/util/qquickpixmapcache.cpp
+++ b/src/quick/util/qquickpixmapcache.cpp
@@ -1,46 +1,11 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQuick module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QtQuick/private/qquickpixmapcache_p.h>
#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>
@@ -63,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>
@@ -70,25 +36,75 @@
#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)
+#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
+# define USE_THREADED_DOWNLOAD 1
+#else
+# define USE_THREADED_DOWNLOAD 0
+#endif
+
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(); }
-#ifndef QT_NO_DEBUG
-static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
+ 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!");
+ }
+
+ 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)
{
@@ -140,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();
@@ -166,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;
};
@@ -198,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)
@@ -212,12 +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;
- QQuickPixmapReaderThreadObject *threadObject;
+ 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();
@@ -226,19 +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;
};
+#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),
@@ -249,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),
@@ -263,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),
@@ -278,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),
@@ -292,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();
+ void release(QQuickPixmapCache *store = nullptr);
void addToCache();
- void removeFromCache();
+ void removeFromCache(QQuickPixmapCache *store = nullptr);
uint refCount;
int frameCount;
@@ -327,11 +383,15 @@ public:
QQuickImageProviderOptions::AutoTransform appliedTransform;
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;
@@ -339,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)
@@ -375,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;
}
@@ -419,7 +482,8 @@ static void maybeRemoveAlpha(QImage *image)
static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize, int *frameCount,
const QRect &requestRegion, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions,
- QQuickImageProviderOptions::AutoTransform *appliedTransform = nullptr, int frame = 0)
+ QQuickImageProviderOptions::AutoTransform *appliedTransform = nullptr, int frame = 0,
+ qreal devicePixelRatio = 1.0)
{
QImageReader imgio(dev);
if (providerOptions.autoTransform() != QQuickImageProviderOptions::UsePluginDefaultTransform)
@@ -433,7 +497,7 @@ static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *e
if (frameCount)
*frameCount = imgio.imageCount();
- QSize scSize = QQuickImageProviderWithOptions::loadSize(imgio.size(), requestSize, imgio.format(), providerOptions);
+ QSize scSize = QQuickImageProviderWithOptions::loadSize(imgio.size(), requestSize, imgio.format(), providerOptions, devicePixelRatio);
if (scSize.isValid())
imgio.setScaledSize(scSize);
if (!requestRegion.isNull())
@@ -488,6 +552,8 @@ public:
}
bool hasOpenGL;
QStringList fileSuffixes;
+private:
+ Q_DISABLE_COPY(BackendSupport)
};
Q_GLOBAL_STATIC(BackendSupport, backendSupport);
@@ -499,7 +565,7 @@ static QString existingImageFileForPath(const QString &localFile)
return localFile;
QString tryFile = localFile + QStringLiteral(".xxxx");
- const int suffixIdx = localFile.length() + 1;
+ const int suffixIdx = localFile.size() + 1;
for (const QString &suffix : backendSupport()->fileSuffixes) {
tryFile.replace(suffixIdx, 10, suffix);
if (QFileInfo::exists(tryFile))
@@ -509,86 +575,107 @@ static QString existingImageFileForPath(const QString &localFile)
}
QQuickPixmapReader::QQuickPixmapReader(QQmlEngine *eng)
-: QThread(eng), engine(eng), threadObject(nullptr)
+: QThread(eng), engine(eng)
#if QT_CONFIG(qml_network)
, 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);
+ QObject::connect(eventLoopQuitHack, &QObject::destroyed, this, &QThread::quit, Qt::DirectConnection);
start(QThread::LowestPriority);
-#if !QT_CONFIG(thread)
- // call nonblocking run ourself, as nothread qthread does not
- run();
+#else
+ run(); // Call nonblocking run for ourselves.
#endif
}
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 : qAsConst(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 : qAsConst(networkJobs))
- cancelJob(reply);
+ for (auto *reply : std::as_const(networkJobs))
+ cancelJob(reply);
- for (auto *reply : qAsConst(asyncResponses))
- cancelJob(reply);
+ for (auto *reply : std::as_const(asyncResponses))
+ cancelJob(reply);
#endif
- if (threadObject) threadObject->processJobs();
- mutex.unlock();
+#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 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
+ // more replies. No one will care about them anymore.
+
+ auto deleteReply = [](QQuickPixmapReply *reply) {
+ if (reply->data && reply->data->reply == reply)
+ reply->data->reply = nullptr;
+ delete reply;
+ };
+
+ for (QQuickPixmapReply *reply : std::as_const(networkJobs))
+ deleteReply(reply);
+
+ for (QQuickPixmapReply *reply : std::as_const(asyncResponses))
+ deleteReply(reply);
+
+ networkJobs.clear();
+ asyncResponses.clear();
+#endif
}
#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;
QSize readSize;
+ QQuickTextureFactory *factory = nullptr;
if (reply->error()) {
error = QQuickPixmapReply::Loading;
errorString = reply->errorString();
@@ -596,29 +683,44 @@ void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
QByteArray all = reply->readAll();
QBuffer buff(&all);
buff.open(QIODevice::ReadOnly);
- int frameCount;
- int const frame = job->data ? job->data->frame : 0;
- if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, &frameCount,
- job->requestRegion, job->requestSize, job->providerOptions, nullptr, frame))
- error = QQuickPixmapReply::Decoding;
- else if (job->data)
- job->data->frameCount = frameCount;
+ QSGTextureReader texReader(&buff, reply->url().fileName());
+ if (backendSupport()->hasOpenGL && texReader.isTexture()) {
+ factory = texReader.read();
+ if (factory) {
+ readSize = factory->textureSize();
+ } else {
+ error = QQuickPixmapReply::Decoding;
+ errorString = QQuickPixmap::tr("Error decoding: %1").arg(reply->url().toString());
+ }
+ } else {
+ int frameCount;
+ int const frame = job->data ? job->data->frame : 0;
+ if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, &frameCount,
+ job->requestRegion, job->requestSize, job->providerOptions, nullptr, frame))
+ error = QQuickPixmapReply::Decoding;
+ else if (job->data)
+ job->data->frameCount = frameCount;
+ }
}
// send completion event to the QQuickPixmapReply
- mutex.lock();
- if (!cancelled.contains(job))
- job->postReply(error, errorString, readSize, QQuickTextureFactory::textureFactoryForImage(image));
- mutex.unlock();
+ if (!factory)
+ factory = QQuickTextureFactory::textureFactoryForImage(image);
+
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(job))
+ job->postReply(error, errorString, readSize, factory);
}
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) {
@@ -630,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());
@@ -672,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);
@@ -685,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.count(); ++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) {
@@ -715,13 +817,13 @@ void QQuickPixmapReader::processJobs()
// deleteLater, since not owned by this thread
job->deleteLater();
}
- cancelled.clear();
+ cancelledJobs.clear();
}
if (!jobs.isEmpty()) {
// Find a job we can use
bool usableJob = false;
- for (int i = jobs.count() - 1; !usableJob && i >= 0; i--) {
+ for (int i = jobs.size() - 1; !usableJob && i >= 0; i--) {
QQuickPixmapReply *job = jobs.at(i);
const QUrl url = job->url;
QString localFile;
@@ -739,12 +841,11 @@ void QQuickPixmapReader::processJobs()
localFile = QQmlFile::urlToLocalFileOrQrc(url);
usableJob = !localFile.isEmpty()
#if QT_CONFIG(qml_network)
- || networkJobs.count() < IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT
+ || networkJobs.size() < IMAGEREQUEST_MAX_NETWORK_REQUEST_COUNT
#endif
;
}
-
if (usableJob) {
jobs.removeAt(i);
@@ -752,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();
}
}
@@ -767,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
@@ -774,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;
}
@@ -805,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;
}
@@ -826,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;
}
@@ -847,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;
}
@@ -867,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
@@ -877,9 +987,12 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
}
// Might be that the async provider was so quick it emitted the signal before we
// could connect to it.
+ //
+ // 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);
@@ -893,45 +1006,58 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
QImage image;
QQuickPixmapReply::ReadError errorCode = QQuickPixmapReply::NoError;
QString errorStr;
- QFile f(existingImageFileForPath(localFile));
QSize readSize;
- if (f.open(QIODevice::ReadOnly)) {
- QSGTextureReader texReader(&f, localFile);
- if (backendSupport()->hasOpenGL && texReader.isTexture()) {
- QQuickTextureFactory *factory = texReader.read();
- if (factory) {
- readSize = factory->textureSize();
+
+ if (runningJob->data && runningJob->data->specialDevice) {
+ int frameCount;
+ if (!readImage(url, runningJob->data->specialDevice, &image, &errorStr, &readSize, &frameCount,
+ runningJob->requestRegion, runningJob->requestSize,
+ runningJob->providerOptions, nullptr, runningJob->data->frame)) {
+ errorCode = QQuickPixmapReply::Loading;
+ } else if (runningJob->data) {
+ runningJob->data->frameCount = frameCount;
+ }
+ } else {
+ QFile f(existingImageFileForPath(localFile));
+ if (f.open(QIODevice::ReadOnly)) {
+ QSGTextureReader texReader(&f, localFile);
+ if (backendSupport()->hasOpenGL && texReader.isTexture()) {
+ QQuickTextureFactory *factory = texReader.read();
+ if (factory) {
+ readSize = factory->textureSize();
+ } else {
+ errorStr = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
+ if (f.fileName() != localFile)
+ errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
+ errorCode = QQuickPixmapReply::Decoding;
+ }
+ PIXMAP_READER_LOCK();
+ if (!cancelledJobs.contains(runningJob))
+ runningJob->postReply(errorCode, errorStr, readSize, factory);
+ return;
} else {
- errorStr = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
- if (f.fileName() != localFile)
- errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
- errorCode = QQuickPixmapReply::Decoding;
+ int frameCount;
+ int const frame = runningJob->data ? runningJob->data->frame : 0;
+ if (!readImage(url, &f, &image, &errorStr, &readSize, &frameCount,
+ runningJob->requestRegion, runningJob->requestSize,
+ runningJob->providerOptions, nullptr, frame)) {
+ errorCode = QQuickPixmapReply::Loading;
+ if (f.fileName() != localFile)
+ errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
+ } else if (runningJob->data) {
+ runningJob->data->frameCount = frameCount;
+ }
}
- mutex.lock();
- if (!cancelled.contains(runningJob))
- runningJob->postReply(errorCode, errorStr, readSize, factory);
- mutex.unlock();
- return;
} else {
- int frameCount;
- int const frame = runningJob->data ? runningJob->data->frame : 0;
- if (!readImage(url, &f, &image, &errorStr, &readSize, &frameCount, runningJob->requestRegion, runningJob->requestSize,
- runningJob->providerOptions, nullptr, frame)) {
- errorCode = QQuickPixmapReply::Loading;
- if (f.fileName() != localFile)
- errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
- } else if (runningJob->data) {
- runningJob->data->frameCount = frameCount;
- }
+ errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
+ errorCode = QQuickPixmapReply::Loading;
}
- } else {
- errorStr = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
- 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
@@ -939,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
@@ -970,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.
@@ -996,45 +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();
}
- mutex.lock();
- threadObject = new QQuickPixmapReaderThreadObject(this);
- mutex.unlock();
+#if QT_CONFIG(quick_pixmap_cache_threaded_download)
+ const auto guard = qScopeGuard([this]() {
+ // We need to delete the runLoopReaderThreadExecutionEnforcer from the same thread.
+ PIXMAP_READER_LOCK();
+ delete runLoopReaderThreadExecutionEnforcer;
+ runLoopReaderThreadExecutionEnforcer = nullptr;
+ });
+
+ {
+ PIXMAP_READER_LOCK();
+ Q_ASSERT(!runLoopReaderThreadExecutionEnforcer);
+ runLoopReaderThreadExecutionEnforcer = new ReaderThreadExecutionEnforcer(this);
+ }
processJobs();
exec();
-
-#if QT_CONFIG(thread)
- // nothread exec is empty and returns
- delete threadObject;
- threadObject = nullptr;
+#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 &&
@@ -1049,81 +1182,101 @@ 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();
+ pixmap->release(this);
currRefCount--;
}
}
}
// 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;
}
-void QQuickPixmapStore::unreferencePixmap(QQuickPixmapData *data)
+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;
+}
+
+/*! \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);
@@ -1131,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) {
@@ -1151,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);
@@ -1168,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);
@@ -1185,12 +1350,12 @@ void QQuickPixmapStore::shrinkCache(int remove)
remove -= data->cost();
m_unreferencedCost -= data->cost();
}
- data->removeFromCache();
+ data->removeFromCache(this);
delete data;
}
}
-void QQuickPixmapStore::timerEvent(QTimerEvent *)
+void QQuickPixmapCache::timerEvent(QTimerEvent *)
{
int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
@@ -1202,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();
}
}
@@ -1274,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()
+void QQuickPixmapData::release(QQuickPixmapCache *store)
{
Q_ASSERT(refCount > 0);
--refCount;
@@ -1294,47 +1460,72 @@ void QQuickPixmapData::release()
QQuickPixmapReader::readerMutex.unlock();
}
+ store = store ? store : QQuickPixmapCache::instance();
if (pixmapStatus == QQuickPixmap::Ready
#ifdef Q_OS_WEBOS
&& storeToCache
#endif
) {
if (inCache)
- pixmapStore()->unreferencePixmap(this);
+ store->unreferencePixmap(this);
else
delete this;
} else {
- removeFromCache();
+ removeFromCache(store);
delete this;
}
}
}
+/*! \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.count()));
+ url, QQuickPixmapCache::instance()->m_cache.size()));
}
}
-void QQuickPixmapData::removeFromCache()
+void QQuickPixmapData::removeFromCache(QQuickPixmapCache *store)
{
if (inCache) {
+ if (!store)
+ store = QQuickPixmapCache::instance();
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
- pixmapStore()->m_cache.remove(key);
+ 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, pixmapStore()->m_cache.count()));
+ 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)
+ const QQuickImageProviderOptions &providerOptions, int frame, bool *ok,
+ qreal devicePixelRatio)
{
if (url.scheme() == QLatin1String("image")) {
QSize readSize;
@@ -1349,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:
{
@@ -1357,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;
@@ -1369,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);
}
@@ -1381,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);
}
@@ -1395,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()));
}
@@ -1413,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());
@@ -1424,9 +1615,10 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
QImage image;
QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform();
int frameCount;
- if (readImage(url, &f, &image, &errorString, &readSize, &frameCount, requestRegion, requestSize, providerOptions, &appliedTransform, frame)) {
+ 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());
@@ -1435,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);
}
@@ -1457,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)
{
@@ -1465,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();
}
@@ -1473,7 +1671,6 @@ QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
QQuickPixmap::~QQuickPixmap()
{
if (d) {
- d->declarativePixmaps.remove(this);
d->release();
d = nullptr;
}
@@ -1581,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);
}
}
@@ -1641,17 +1844,18 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
}
void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
- QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount)
+ QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount,
+ 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
@@ -1675,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>()) {
@@ -1691,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);
+ d = createPixmapDataSync(engine, url, requestRegion, requestSize, providerOptions, frame, &ok, devicePixelRatio);
if (ok) {
PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height())));
if (options & QQuickPixmap::Cache)
@@ -1711,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();
@@ -1720,19 +1925,67 @@ 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();
+ qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame;
+ }
+}
+
+/*! \internal
+ Attempts to load an image from the given \a url via the given \a device.
+ This is for special cases when the QImageIOHandler can benefit from reusing
+ the I/O device, or from something extra that a subclass of QIODevice
+ carries with it. So far, this code doesn't support loading anything other
+ than a QImage, for example compressed textures. It can be added if needed.
+*/
+void QQuickPixmap::loadImageFromDevice(QQmlEngine *engine, QIODevice *device, const QUrl &url,
+ const QRect &requestRegion, const QSize &requestSize,
+ const QQuickImageProviderOptions &providerOptions, int frame, int frameCount)
+{
+ auto oldD = d;
+ QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
+ 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;
+
+ locker.unlock();
+ d = new QQuickPixmapData(url, requestRegion, requestSize, providerOptions,
+ QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
+ d->specialDevice = device;
+ d->addToCache();
+
+ QQuickPixmapReader::readerMutex.lock();
+ QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
+ d->reply = reader->getImage(d);
+ if (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;
}
@@ -1743,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;
}
@@ -1753,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) {
@@ -1775,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)
@@ -1785,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)
@@ -1795,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