From d2f947aee131832b008a2f8a2dd2fb9beafdbdb6 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 10 Mar 2014 10:49:38 -0700 Subject: [PATCH 1/2] Working on LRU cache for resources. --- interface/src/renderer/GeometryCache.cpp | 12 +++++- interface/src/renderer/GeometryCache.h | 1 + interface/src/renderer/TextureCache.cpp | 52 ++++++++++++++---------- interface/src/renderer/TextureCache.h | 3 ++ libraries/metavoxels/src/ScriptCache.cpp | 2 +- libraries/shared/src/ResourceCache.cpp | 42 +++++++++++++++++++ libraries/shared/src/ResourceCache.h | 16 +++++++- 7 files changed, 103 insertions(+), 25 deletions(-) diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 222edcf46a..a7973f4a1b 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -294,7 +294,8 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons QSharedPointer GeometryCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - QSharedPointer geometry(new NetworkGeometry(url, fallback.staticCast(), delayLoad)); + QSharedPointer geometry(new NetworkGeometry(url, fallback.staticCast(), delayLoad), + &Resource::allReferencesCleared); geometry->setLODParent(geometry); return geometry.staticCast(); } @@ -536,6 +537,15 @@ void NetworkGeometry::downloadFinished(QNetworkReply* reply) { QThreadPool::globalInstance()->start(new GeometryReader(_self, url, reply, _mapping)); } +void NetworkGeometry::reinsert() { + Resource::reinsert(); + + _lodParent = qWeakPointerCast(_self); + foreach (const QSharedPointer& lod, _lods) { + lod->setLODParent(_lodParent); + } +} + void NetworkGeometry::setGeometry(const FBXGeometry& geometry) { _geometry = geometry; diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index 8c59ee7dc5..6e9604e79e 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -89,6 +89,7 @@ public: protected: virtual void downloadFinished(QNetworkReply* reply); + virtual void reinsert() const; Q_INVOKABLE void setGeometry(const FBXGeometry& geometry); diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 9f67b3bd0b..e3f750e676 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -128,9 +128,12 @@ QSharedPointer TextureCache::getTexture(const QUrl& url, bool no } QSharedPointer texture = _dilatableNetworkTextures.value(url); if (texture.isNull()) { - texture = QSharedPointer(new DilatableNetworkTexture(url)); + texture = QSharedPointer(new DilatableNetworkTexture(url), &Resource::allReferencesCleared); texture->setSelf(texture); + texture->setCache(this); _dilatableNetworkTextures.insert(url, texture); + } else { + _unusedResources.removeOne(texture); } return texture; } @@ -229,7 +232,7 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) { QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkTexture(url, *(const bool*)extra)); + return QSharedPointer(new NetworkTexture(url, *(const bool*)extra), &Resource::allReferencesCleared); } QOpenGLFramebufferObject* TextureCache::createFramebufferObject() { @@ -352,26 +355,6 @@ DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) : { } -void DilatableNetworkTexture::imageLoaded(const QImage& image) { - _image = image; - - // scan out from the center to find inner and outer radii - int halfWidth = image.width() / 2; - int halfHeight = image.height() / 2; - const int BLACK_THRESHOLD = 32; - while (_innerRadius < halfWidth && qGray(image.pixel(halfWidth + _innerRadius, halfHeight)) < BLACK_THRESHOLD) { - _innerRadius++; - } - _outerRadius = _innerRadius; - const int TRANSPARENT_THRESHOLD = 32; - while (_outerRadius < halfWidth && qAlpha(image.pixel(halfWidth + _outerRadius, halfHeight)) > TRANSPARENT_THRESHOLD) { - _outerRadius++; - } - - // clear out any textures we generated before loading - _dilatedTextures.clear(); -} - QSharedPointer DilatableNetworkTexture::getDilatedTexture(float dilation) { QSharedPointer texture = _dilatedTextures.value(dilation); if (texture.isNull()) { @@ -400,3 +383,28 @@ QSharedPointer DilatableNetworkTexture::getDilatedTexture(float dilatio return texture; } +void DilatableNetworkTexture::imageLoaded(const QImage& image) { + _image = image; + + // scan out from the center to find inner and outer radii + int halfWidth = image.width() / 2; + int halfHeight = image.height() / 2; + const int BLACK_THRESHOLD = 32; + while (_innerRadius < halfWidth && qGray(image.pixel(halfWidth + _innerRadius, halfHeight)) < BLACK_THRESHOLD) { + _innerRadius++; + } + _outerRadius = _innerRadius; + const int TRANSPARENT_THRESHOLD = 32; + while (_outerRadius < halfWidth && qAlpha(image.pixel(halfWidth + _outerRadius, halfHeight)) > TRANSPARENT_THRESHOLD) { + _outerRadius++; + } + + // clear out any textures we generated before loading + _dilatedTextures.clear(); +} + +void DilatableNetworkTexture::reinsert() { + static_cast(_cache.data())->_dilatableNetworkTextures.insert(_url, + qWeakPointerCast(_self)); +} + diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index dba34a0d0e..0111a2826d 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -76,6 +76,8 @@ protected: private: + friend class DilatableNetworkTexture; + QOpenGLFramebufferObject* createFramebufferObject(); GLuint _permutationNormalTextureID; @@ -151,6 +153,7 @@ public: protected: virtual void imageLoaded(const QImage& image); + virtual void reinsert(); private: diff --git a/libraries/metavoxels/src/ScriptCache.cpp b/libraries/metavoxels/src/ScriptCache.cpp index 702a36de2a..cabf075563 100644 --- a/libraries/metavoxels/src/ScriptCache.cpp +++ b/libraries/metavoxels/src/ScriptCache.cpp @@ -51,7 +51,7 @@ QSharedPointer ScriptCache::getValue(const ParameterizedURL& url) QSharedPointer ScriptCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkProgram(this, url)); + return QSharedPointer(new NetworkProgram(this, url), &Resource::allReferencesCleared); } NetworkProgram::NetworkProgram(ScriptCache* cache, const QUrl& url) : diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp index a7c0302a9c..6434376cd8 100644 --- a/libraries/shared/src/ResourceCache.cpp +++ b/libraries/shared/src/ResourceCache.cpp @@ -18,6 +18,13 @@ ResourceCache::ResourceCache(QObject* parent) : QObject(parent) { } +ResourceCache::~ResourceCache() { + // make sure our unused resources know we're out of commission + foreach (const QSharedPointer& resource, _unusedResources) { + resource->setCache(NULL); + } +} + QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) { if (!url.isValid() && fallback.isValid()) { return getResource(fallback, QUrl(), delayLoad); @@ -27,11 +34,25 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& resource = createResource(url, fallback.isValid() ? getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); resource->setSelf(resource); + resource->setCache(this); _resources.insert(url, resource); + + } else { + _unusedResources.removeOne(resource); } return resource; } +void ResourceCache::addUnusedResource(const QSharedPointer& resource) { + const int RETAINED_RESOURCE_COUNT = 1; + if (_unusedResources.size() > RETAINED_RESOURCE_COUNT) { + // unload the oldest resource + QSharedPointer oldResource = _unusedResources.takeFirst(); + oldResource->setCache(NULL); + } + _unusedResources.append(resource); +} + void ResourceCache::attemptRequest(Resource* resource) { if (_requestLimit <= 0) { // wait until a slot becomes available @@ -74,6 +95,7 @@ int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT; QList > ResourceCache::_pendingRequests; Resource::Resource(const QUrl& url, bool delayLoad) : + _url(url), _request(url), _startedLoading(false), _failedToLoad(false), @@ -141,6 +163,22 @@ float Resource::getLoadPriority() { return highestPriority; } +void Resource::allReferencesCleared() { + if (_cache) { + // create and reinsert new shared pointer + QSharedPointer self(this, &Resource::allReferencesCleared); + setSelf(self); + reinsert(); + + // add to the unused list + _cache->_unusedResources.append(self); + + } else { + qDebug() << "deleting" << _url; + delete this; + } +} + void Resource::attemptRequest() { _startedLoading = true; ResourceCache::attemptRequest(this); @@ -155,6 +193,10 @@ void Resource::finishedLoading(bool success) { _loadPriorities.clear(); } +void Resource::reinsert() { + _cache->_resources.insert(_url, _self); +} + const int REPLY_TIMEOUT_MS = 5000; void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { diff --git a/libraries/shared/src/ResourceCache.h b/libraries/shared/src/ResourceCache.h index 8aed2bfcb0..2826902598 100644 --- a/libraries/shared/src/ResourceCache.h +++ b/libraries/shared/src/ResourceCache.h @@ -38,9 +38,12 @@ public: static int getRequestLimit() { return _requestLimit; } ResourceCache(QObject* parent = NULL); + virtual ~ResourceCache(); protected: + QList > _unusedResources; + /// Loads a resource from the specified URL. /// \param fallback a fallback URL to load if the desired one is unavailable /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested @@ -52,13 +55,15 @@ protected: virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) = 0; + void addUnusedResource(const QSharedPointer& resource); + static void attemptRequest(Resource* resource); static void requestCompleted(); private: friend class Resource; - + QHash > _resources; static QNetworkAccessManager* _networkAccessManager; @@ -95,6 +100,10 @@ public: void setSelf(const QWeakPointer& self) { _self = self; } + void setCache(ResourceCache* cache) { _cache = cache; } + + void allReferencesCleared(); + protected slots: void attemptRequest(); @@ -107,12 +116,17 @@ protected: /// Should be called by subclasses when all the loading that will be done has been done. Q_INVOKABLE void finishedLoading(bool success); + /// Reinserts this resource into the cache. + virtual void reinsert(); + + QUrl _url; QNetworkRequest _request; bool _startedLoading; bool _failedToLoad; bool _loaded; QHash, float> _loadPriorities; QWeakPointer _self; + QPointer _cache; private slots: From 34ddddae1b8449c90911fb74288c72f728c5a392 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 10 Mar 2014 12:06:07 -0700 Subject: [PATCH 2/2] Basic LRU cache. Closes #2234. --- interface/src/renderer/GeometryCache.h | 2 +- interface/src/renderer/TextureCache.cpp | 2 +- libraries/shared/src/ResourceCache.cpp | 25 +++++++++++++++++-------- libraries/shared/src/ResourceCache.h | 10 +++++++++- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index 6e9604e79e..da1955039d 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -89,7 +89,7 @@ public: protected: virtual void downloadFinished(QNetworkReply* reply); - virtual void reinsert() const; + virtual void reinsert(); Q_INVOKABLE void setGeometry(const FBXGeometry& geometry); diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index e3f750e676..80eef66ff5 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -133,7 +133,7 @@ QSharedPointer TextureCache::getTexture(const QUrl& url, bool no texture->setCache(this); _dilatableNetworkTextures.insert(url, texture); } else { - _unusedResources.removeOne(texture); + _unusedResources.remove(texture->getLRUKey()); } return texture; } diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp index 6434376cd8..65c68b23ef 100644 --- a/libraries/shared/src/ResourceCache.cpp +++ b/libraries/shared/src/ResourceCache.cpp @@ -15,7 +15,8 @@ #include "ResourceCache.h" ResourceCache::ResourceCache(QObject* parent) : - QObject(parent) { + QObject(parent), + _lastLRUKey(0) { } ResourceCache::~ResourceCache() { @@ -38,19 +39,21 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& _resources.insert(url, resource); } else { - _unusedResources.removeOne(resource); + _unusedResources.remove(resource->getLRUKey()); } return resource; } void ResourceCache::addUnusedResource(const QSharedPointer& resource) { - const int RETAINED_RESOURCE_COUNT = 1; + const int RETAINED_RESOURCE_COUNT = 50; if (_unusedResources.size() > RETAINED_RESOURCE_COUNT) { // unload the oldest resource - QSharedPointer oldResource = _unusedResources.takeFirst(); - oldResource->setCache(NULL); + QMap >::iterator it = _unusedResources.begin(); + it.value()->setCache(NULL); + _unusedResources.erase(it); } - _unusedResources.append(resource); + resource->setLRUKey(++_lastLRUKey); + _unusedResources.insert(resource->getLRUKey(), resource); } void ResourceCache::attemptRequest(Resource* resource) { @@ -100,6 +103,7 @@ Resource::Resource(const QUrl& url, bool delayLoad) : _startedLoading(false), _failedToLoad(false), _loaded(false), + _lruKey(0), _reply(NULL), _attempts(0) { @@ -171,10 +175,9 @@ void Resource::allReferencesCleared() { reinsert(); // add to the unused list - _cache->_unusedResources.append(self); + _cache->addUnusedResource(self); } else { - qDebug() << "deleting" << _url; delete this; } } @@ -231,6 +234,7 @@ void Resource::makeRequest() { connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); + connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished())); _replyTimer = new QTimer(this); connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout())); @@ -275,6 +279,11 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) } } +void Resource::handleReplyFinished() { + qDebug() << "Got finished without download progress/error?" << _url; + handleDownloadProgress(0, 0); +} + uint qHash(const QPointer& value, uint seed) { return qHash(value.data(), seed); } diff --git a/libraries/shared/src/ResourceCache.h b/libraries/shared/src/ResourceCache.h index 2826902598..a544f43731 100644 --- a/libraries/shared/src/ResourceCache.h +++ b/libraries/shared/src/ResourceCache.h @@ -42,7 +42,7 @@ public: protected: - QList > _unusedResources; + QMap > _unusedResources; /// Loads a resource from the specified URL. /// \param fallback a fallback URL to load if the desired one is unavailable @@ -65,6 +65,7 @@ private: friend class Resource; QHash > _resources; + int _lastLRUKey; static QNetworkAccessManager* _networkAccessManager; static int _requestLimit; @@ -80,6 +81,9 @@ public: Resource(const QUrl& url, bool delayLoad = false); ~Resource(); + /// Returns the key last used to identify this resource in the unused map. + int getLRUKey() const { return _lruKey; } + /// Makes sure that the resource has started loading. void ensureLoading(); @@ -132,16 +136,20 @@ private slots: void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void handleReplyError(); + void handleReplyFinished(); void handleReplyTimeout(); private: + void setLRUKey(int lruKey) { _lruKey = lruKey; } + void makeRequest(); void handleReplyError(QNetworkReply::NetworkError error, QDebug debug); friend class ResourceCache; + int _lruKey; QNetworkReply* _reply; QTimer* _replyTimer; qint64 _bytesReceived;