mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 03:13:51 +02:00
Merge pull request #9952 from jherico/smarter_textures
Return existing textures when matching hash for a previously found live texture
This commit is contained in:
commit
5a6478c660
5 changed files with 112 additions and 66 deletions
|
@ -40,5 +40,8 @@ KTXFile::KTXFile(Metadata&& metadata, const std::string& filepath) :
|
|||
|
||||
std::unique_ptr<ktx::KTX> KTXFile::getKTX() const {
|
||||
ktx::StoragePointer storage = std::make_shared<storage::FileStorage>(getFilepath().c_str());
|
||||
return ktx::KTX::create(storage);
|
||||
if (*storage) {
|
||||
return ktx::KTX::create(storage);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -188,6 +188,35 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, Type type, const
|
|||
return ResourceCache::getResource(url, QUrl(), &extra).staticCast<NetworkTexture>();
|
||||
}
|
||||
|
||||
gpu::TexturePointer TextureCache::getTextureByHash(const std::string& hash) {
|
||||
std::weak_ptr<gpu::Texture> weakPointer;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_texturesByHashesMutex);
|
||||
weakPointer = _texturesByHashes[hash];
|
||||
}
|
||||
auto result = weakPointer.lock();
|
||||
if (result) {
|
||||
qCWarning(modelnetworking) << "QQQ Returning live texture for hash " << hash.c_str();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
gpu::TexturePointer TextureCache::cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture) {
|
||||
gpu::TexturePointer result;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_texturesByHashesMutex);
|
||||
result = _texturesByHashes[hash].lock();
|
||||
if (!result) {
|
||||
_texturesByHashes[hash] = texture;
|
||||
result = texture;
|
||||
} else {
|
||||
qCWarning(modelnetworking) << "QQQ Swapping out texture with previous live texture in hash " << hash.c_str();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
gpu::TexturePointer getFallbackTextureForType(NetworkTexture::Type type) {
|
||||
gpu::TexturePointer result;
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
|
@ -386,15 +415,6 @@ private:
|
|||
int _maxNumPixels;
|
||||
};
|
||||
|
||||
class KTXReader : public Reader {
|
||||
public:
|
||||
KTXReader(const QWeakPointer<Resource>& resource, const QUrl& url, const KTXFilePointer& ktxFile);
|
||||
void read() override final;
|
||||
|
||||
private:
|
||||
KTXFilePointer _file;
|
||||
};
|
||||
|
||||
void NetworkTexture::downloadFinished(const QByteArray& data) {
|
||||
loadContent(data);
|
||||
}
|
||||
|
@ -408,15 +428,39 @@ void NetworkTexture::loadContent(const QByteArray& content) {
|
|||
hash = hasher.result().toHex().toStdString();
|
||||
}
|
||||
|
||||
std::unique_ptr<Reader> reader;
|
||||
KTXFilePointer ktxFile;
|
||||
if (!_cache.isNull() && (ktxFile = static_cast<TextureCache*>(_cache.data())->_ktxCache.getFile(hash))) {
|
||||
reader.reset(new KTXReader(_self, _url, ktxFile));
|
||||
} else {
|
||||
reader.reset(new ImageReader(_self, _url, content, hash, _maxNumPixels));
|
||||
auto textureCache = static_cast<TextureCache*>(_cache.data());
|
||||
|
||||
if (textureCache != nullptr) {
|
||||
// If we already have a live texture with the same hash, use it
|
||||
auto texture = textureCache->getTextureByHash(hash);
|
||||
|
||||
// If there is no live texture, check if there's an existing KTX file
|
||||
if (!texture) {
|
||||
KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
|
||||
if (ktxFile) {
|
||||
// Ensure that the KTX deserialization worked
|
||||
auto ktx = ktxFile->getKTX();
|
||||
if (ktx) {
|
||||
texture.reset(gpu::Texture::unserialize(ktx));
|
||||
// Ensure that the texture population worked
|
||||
if (texture) {
|
||||
texture->setKtxBacking(ktx);
|
||||
texture = textureCache->cacheTextureByHash(hash, texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found the texture either because it's in use or via KTX deserialization,
|
||||
// set the image and return immediately.
|
||||
if (texture) {
|
||||
setImage(texture, texture->getWidth(), texture->getHeight());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QThreadPool::globalInstance()->start(reader.release());
|
||||
// We failed to find an existing live or KTX texture, so trigger an image reader
|
||||
QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, hash, _maxNumPixels));
|
||||
}
|
||||
|
||||
Reader::Reader(const QWeakPointer<Resource>& resource, const QUrl& url) :
|
||||
|
@ -526,22 +570,34 @@ void ImageReader::read() {
|
|||
texture->setFallbackTexture(networkTexture->getFallbackTexture());
|
||||
}
|
||||
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
// Save the image into a KTXFile
|
||||
auto ktx = gpu::Texture::serialize(*texture);
|
||||
if (ktx) {
|
||||
const char* data = reinterpret_cast<const char*>(ktx->_storage->data());
|
||||
size_t length = ktx->_storage->size();
|
||||
auto memKtx = gpu::Texture::serialize(*texture);
|
||||
if (!memKtx) {
|
||||
qCWarning(modelnetworking) << "Unable to serialize texture to KTX " << _url;
|
||||
}
|
||||
|
||||
if (memKtx && textureCache) {
|
||||
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
|
||||
size_t length = memKtx->_storage->size();
|
||||
KTXFilePointer file;
|
||||
auto& ktxCache = DependencyManager::get<TextureCache>()->_ktxCache;
|
||||
if (!ktx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(_hash, length)))) {
|
||||
auto& ktxCache = textureCache->_ktxCache;
|
||||
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(_hash, length)))) {
|
||||
qCWarning(modelnetworking) << _url << "file cache failed";
|
||||
} else {
|
||||
resource.staticCast<NetworkTexture>()->_file = file;
|
||||
auto ktx = file->getKTX();
|
||||
texture->setKtxBacking(ktx);
|
||||
auto fileKtx = file->getKTX();
|
||||
if (fileKtx) {
|
||||
texture->setKtxBacking(fileKtx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCWarning(modelnetworking) << "Unable to serialize texture to KTX " << _url;
|
||||
}
|
||||
|
||||
// We replace the texture with the one stored in the cache. This deals with the possible race condition of two different
|
||||
// images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will
|
||||
// be the winner
|
||||
if (textureCache) {
|
||||
texture = textureCache->cacheTextureByHash(_hash, texture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -554,35 +610,3 @@ void ImageReader::read() {
|
|||
qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope";
|
||||
}
|
||||
}
|
||||
|
||||
KTXReader::KTXReader(const QWeakPointer<Resource>& resource, const QUrl& url,
|
||||
const KTXFilePointer& ktxFile) :
|
||||
Reader(resource, url), _file(ktxFile) {}
|
||||
|
||||
void KTXReader::read() {
|
||||
PROFILE_RANGE_EX(resource_parse_image_ktx, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } });
|
||||
|
||||
gpu::TexturePointer texture;
|
||||
{
|
||||
auto resource = _resource.lock(); // to ensure the resource is still needed
|
||||
if (!resource) {
|
||||
qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope";
|
||||
return;
|
||||
}
|
||||
|
||||
resource.staticCast<NetworkTexture>()->_file = _file;
|
||||
auto ktx = _file->getKTX();
|
||||
texture.reset(gpu::Texture::unserialize(ktx));
|
||||
texture->setKtxBacking(ktx);
|
||||
}
|
||||
|
||||
auto resource = _resource.lock(); // to ensure the resource is still needed
|
||||
if (resource) {
|
||||
QMetaObject::invokeMethod(resource.data(), "setImage",
|
||||
Q_ARG(gpu::TexturePointer, texture),
|
||||
Q_ARG(int, texture->getWidth()), Q_ARG(int, texture->getHeight()));
|
||||
} else {
|
||||
qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -137,6 +137,10 @@ public:
|
|||
NetworkTexturePointer getTexture(const QUrl& url, Type type = Type::DEFAULT_TEXTURE,
|
||||
const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
|
||||
|
||||
|
||||
gpu::TexturePointer getTextureByHash(const std::string& hash);
|
||||
gpu::TexturePointer cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture);
|
||||
|
||||
protected:
|
||||
// Overload ResourceCache::prefetch to allow specifying texture type for loads
|
||||
Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
|
||||
|
@ -155,6 +159,9 @@ private:
|
|||
static const std::string KTX_DIRNAME;
|
||||
static const std::string KTX_EXT;
|
||||
KTXCache _ktxCache;
|
||||
// Map from image hashes to texture weak pointers
|
||||
std::unordered_map<std::string, std::weak_ptr<gpu::Texture>> _texturesByHashes;
|
||||
std::mutex _texturesByHashesMutex;
|
||||
|
||||
gpu::TexturePointer _permutationNormalTexture;
|
||||
gpu::TexturePointer _whiteTexture;
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
|
||||
#include "Storage.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
|
||||
Q_LOGGING_CATEGORY(storagelogging, "hifi.core.storage")
|
||||
|
||||
using namespace storage;
|
||||
|
||||
|
@ -64,12 +68,15 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u
|
|||
}
|
||||
|
||||
FileStorage::FileStorage(const QString& filename) : _file(filename) {
|
||||
if (!_file.open(QFile::ReadOnly)) {
|
||||
throw std::runtime_error("Unable to open file");
|
||||
}
|
||||
_mapped = _file.map(0, _file.size());
|
||||
if (!_mapped) {
|
||||
throw std::runtime_error("Unable to map file");
|
||||
if (_file.open(QFile::ReadOnly)) {
|
||||
_mapped = _file.map(0, _file.size());
|
||||
if (_mapped) {
|
||||
_valid = true;
|
||||
} else {
|
||||
qCWarning(storagelogging) << "Failed to map file " << filename;
|
||||
}
|
||||
} else {
|
||||
qCWarning(storagelogging) << "Failed to open file " << filename;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace storage {
|
|||
virtual ~Storage() {}
|
||||
virtual const uint8_t* data() const = 0;
|
||||
virtual size_t size() const = 0;
|
||||
virtual operator bool() const { return true; }
|
||||
|
||||
StoragePointer createView(size_t size = 0, size_t offset = 0) const;
|
||||
StoragePointer toFileStorage(const QString& filename) const;
|
||||
|
@ -41,6 +42,7 @@ namespace storage {
|
|||
const uint8_t* data() const override { return _data.data(); }
|
||||
uint8_t* data() { return _data.data(); }
|
||||
size_t size() const override { return _data.size(); }
|
||||
operator bool() const override { return true; }
|
||||
private:
|
||||
std::vector<uint8_t> _data;
|
||||
};
|
||||
|
@ -56,7 +58,9 @@ namespace storage {
|
|||
|
||||
const uint8_t* data() const override { return _mapped; }
|
||||
size_t size() const override { return _file.size(); }
|
||||
operator bool() const override { return _valid; }
|
||||
private:
|
||||
bool _valid { false };
|
||||
QFile _file;
|
||||
uint8_t* _mapped { nullptr };
|
||||
};
|
||||
|
@ -66,6 +70,7 @@ namespace storage {
|
|||
ViewStorage(const storage::StoragePointer& owner, size_t size, const uint8_t* data);
|
||||
const uint8_t* data() const override { return _data; }
|
||||
size_t size() const override { return _size; }
|
||||
operator bool() const override { return *_owner; }
|
||||
private:
|
||||
const storage::StoragePointer _owner;
|
||||
const size_t _size;
|
||||
|
|
Loading…
Reference in a new issue