From ab7099b3eb28b95773ce650ab075c61a8e3cecc2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Sun, 16 Apr 2017 23:16:23 -0700 Subject: [PATCH] Add loading of lower mips to NetworkTexture --- .../gpu/gl45/GL45BackendVariableTexture.cpp | 1 + libraries/gpu/src/gpu/Texture.cpp | 26 +++++++++++++ libraries/gpu/src/gpu/Texture.h | 26 ++++++++++--- libraries/gpu/src/gpu/Texture_ktx.cpp | 36 ++++++++++++++---- libraries/ktx/src/ktx/KTX.h | 4 +- .../src/model-networking/TextureCache.cpp | 37 ++++++++++++++++++- .../src/model-networking/TextureCache.h | 11 ++++-- 7 files changed, 121 insertions(+), 20 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp index 320d694473..c37ee46bfd 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendVariableTexture.cpp @@ -190,6 +190,7 @@ void GL45ResourceTexture::populateTransferQueue() { qDebug() << "populateTransferQueue " << QString::fromStdString(_gpuObject.source()) << sourceMip << " " << targetMip; for (uint8_t face = 0; face < maxFace; ++face) { if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) { + const_cast(_gpuObject).requestInterestInMip(sourceMip); continue; } diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 205cf3a65a..1dbe9db2b4 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -476,6 +476,32 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin } } +void Texture::requestInterestInMip(uint16 level) { + if (!_storage->isMipAvailable(level, 0)) { + std::lock_guard lock(_mipInterestListenersMutex); + for (auto& callback : _mipInterestListeners) { + callback->handleMipInterestCallback(level); + } + } +} + +bool Texture::isStoredMipFaceAvailable(uint16 level, uint8 face) const { + return _storage->isMipAvailable(level, face); +} + +void Texture::registerMipInterestListener(MipInterestListener* listener) { + std::lock_guard lock(_mipInterestListenersMutex); + _mipInterestListeners.push_back(listener); +} + +void Texture::unregisterMipInterestListener(MipInterestListener* listener) { + std::lock_guard lock(_mipInterestListenersMutex); + auto it = find(_mipInterestListeners.begin(), _mipInterestListeners.end(), listener); + if (it != _mipInterestListeners.end()) { + _mipInterestListeners.erase(it); + } +} + void Texture::setAutoGenerateMips(bool enable) { bool changed = false; if (!_autoGenerateMips) { diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 83f1f154d3..8c9d62e3b8 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -310,19 +310,22 @@ public: KtxStorage(const std::string& filename); PixelsPointer getMipFace(uint16 level, uint8 face = 0) const override; Size getMipFaceSize(uint16 level, uint8 face = 0) const override; - // By convention, all mip levels and faces MUST be populated when using KTX backing bool isMipAvailable(uint16 level, uint8 face = 0) const override; - void assignMipData(uint16 level, const storage::StoragePointer& storage) override; - void assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) override; void reset() override { } protected: + std::shared_ptr maybeOpenFile(); + + std::mutex _cacheFileCreateMutex; + std::mutex _cacheFileWriteMutex; + std::weak_ptr _cacheFile; + std::string _filename; - uint8_t _minMipLevelAvailable; - //storage::FileStorage _cacheFile; + std::atomic _minMipLevelAvailable; + ktx::KTXDescriptorPointer _ktxDescriptor; friend class Texture; }; @@ -470,7 +473,7 @@ public: // Access the stored mips and faces const PixelsPointer accessStoredMipFace(uint16 level, uint8 face = 0) const { return _storage->getMipFace(level, face); } - bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const { return _storage->isMipAvailable(level, face); } + bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const;// { return _storage->isMipAvailable(level, face); } Size getStoredMipFaceSize(uint16 level, uint8 face = 0) const { return _storage->getMipFaceSize(level, face); } Size getStoredMipSize(uint16 level) const; Size getStoredSize() const; @@ -478,6 +481,17 @@ public: void setStorage(std::unique_ptr& newStorage); void setKtxBacking(const std::string& filename); + class MipInterestListener { + public: + virtual void handleMipInterestCallback(uint16 level) = 0; + }; + void registerMipInterestListener(MipInterestListener* listener); + void unregisterMipInterestListener(MipInterestListener* listener); + std::vector _mipInterestListeners; + std::mutex _mipInterestListenersMutex; + + void requestInterestInMip(uint16 level); + // Usage is a a set of flags providing Semantic about the usage of the Texture. void setUsage(const Usage& usage) { _usage = usage; } Usage getUsage() const { return _usage; } diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index d9a0348e54..0ea9f2ce4d 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -69,6 +69,19 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) { } } +std::shared_ptr KtxStorage::maybeOpenFile() { + std::shared_ptr file = _cacheFile.lock(); + if (file) { + return file; + } + + std::lock_guard lock { _cacheFileCreateMutex }; + file = std::make_shared(_filename.c_str()); + _cacheFile = file; + + return file; +} + PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { qDebug() << "getMipFace: " << QString::fromStdString(_filename) << ": " << level << " " << face; storage::StoragePointer result; @@ -86,9 +99,8 @@ Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const { bool KtxStorage::isMipAvailable(uint16 level, uint8 face) const { - auto minLevel = _minMipLevelAvailable; - auto avail = level >= minLevel; - qDebug() << "isMipAvailable: " << QString::fromStdString(_filename) << ": " << level << " " << face << avail << minLevel << " " << _ktxDescriptor->header.numberOfMipmapLevels; + auto avail = level >= _minMipLevelAvailable; + qDebug() << "isMipAvailable: " << QString::fromStdString(_filename) << ": " << level << " " << face << avail << _minMipLevelAvailable << " " << _ktxDescriptor->header.numberOfMipmapLevels; //return true; return avail; } @@ -108,8 +120,9 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor } - auto fileStorage = new storage::FileStorage(_filename.c_str()); - ktx::StoragePointer file { fileStorage }; + //auto fileStorage = new storage::FileStorage(_filename.c_str()); + //ktx::StoragePointer file { fileStorage }; + auto file = maybeOpenFile(); auto data = file->mutableData(); data += file->size(); @@ -119,8 +132,17 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor data -= 4; } data += 4; - memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize); - _minMipLevelAvailable = level; + { + std::lock_guard lock { _cacheFileWriteMutex }; + + if (level != _minMipLevelAvailable - 1) { + qWarning() << "Invalid level to be stored"; + return; + } + + memcpy(data, storage->data(), _ktxDescriptor->images[level]._imageSize); + _minMipLevelAvailable = level; + } } void KtxStorage::assignMipFaceData(uint16 level, uint8 face, const storage::StoragePointer& storage) { diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 7056f22ba8..e8ed7da8e3 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -70,7 +70,7 @@ end namespace ktx { - const std::string HIFI_MIN_POPULATED_MIP_KEY = "hifiMinMip"; + const std::string HIFI_MIN_POPULATED_MIP_KEY = "hifi.minMip"; const uint32_t PACKING_SIZE { sizeof(uint32_t) }; @@ -414,10 +414,10 @@ namespace ktx { struct ImageHeader { using FaceOffsets = std::vector; using FaceBytes = std::vector; + // This is the byte offset from the _start_ of the image region. For example, level 0 // will have a byte offset of 0. const uint32_t _imageOffset; - const uint32_t _numFaces; const uint32_t _imageSize; const uint32_t _faceSize; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 592413e2bc..e45c353aac 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -244,6 +244,10 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) { return result; } +NetworkTexture::~NetworkTexture() { + _textureSource->getGPUTexture()->unregisterMipInterestListener(this); +} + /// Returns a texture version of an image file gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) { QImage image = QImage(path); @@ -371,6 +375,21 @@ void NetworkTexture::makeRequest() { startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL); } +void NetworkTexture::handleMipInterestCallback(uint16_t level) { + QMetaObject::invokeMethod(this, "handleMipInterestLevel", Qt::QueuedConnection, Q_ARG(uint16_t, level)); +} + +void NetworkTexture::handleMipInterestLevel(uint16_t level) { + _lowestRequestedMipLevel = std::min(level, _lowestRequestedMipLevel); + if (!_ktxMipRequest) { + startRequestForNextMipLevel(); + } +} + +void NetworkTexture::startRequestForNextMipLevel() { + startMipRangeRequest(std::max(0, _lowestKnownPopulatedMip - 1), std::max(0, _lowestKnownPopulatedMip - 1)); +} + // Load mips in the range [low, high] (inclusive) void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) { if (_ktxMipRequest) { @@ -430,6 +449,8 @@ void NetworkTexture::ktxMipRequestFinished() { if (_ktxMipRequest->getResult() == ResourceRequest::Success) { if (_initialKtxLoaded) { assert(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0); + + _lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first; _textureSource->getGPUTexture()->assignStoredMip(_ktxMipLevelRangeInFlight.first, _ktxMipRequest->getData().size(), reinterpret_cast(_ktxMipRequest->getData().data())); @@ -493,6 +514,7 @@ void NetworkTexture::maybeCreateKTX() { texture.reset(gpu::Texture::unserialize(_file->getFilepath(), *_ktxDescriptor)); texture->setKtxBacking(file->getFilepath()); texture->setSource(filename); + texture->registerMipInterestListener(this); auto& images = _ktxDescriptor->images; size_t imageSizeRemaining = _ktxHighMipData.size(); @@ -521,6 +543,15 @@ void NetworkTexture::maybeCreateKTX() { texture = textureCache->cacheTextureByHash(filename, texture); } + + _lowestKnownPopulatedMip = _ktxDescriptor->header.numberOfMipmapLevels; + for (uint16_t l = 0; l < 200; l++) { + if (texture->isStoredMipFaceAvailable(l)) { + _lowestKnownPopulatedMip = l; + break; + } + } + setImage(texture, header->getPixelWidth(), header->getPixelHeight()); @@ -528,7 +559,8 @@ void NetworkTexture::maybeCreateKTX() { { QTimer* timer = new QTimer(); connect(timer, &QTimer::timeout, this, [=]() { - startMipRangeRequest(level, level); + //startMipRangeRequest(level, level); + startRequestForNextMipLevel(); }); timer->setSingleShot(true); timer->setInterval(4000); @@ -538,7 +570,8 @@ void NetworkTexture::maybeCreateKTX() { { QTimer* timer = new QTimer(); connect(timer, &QTimer::timeout, this, [=]() { - startMipRangeRequest(level - 1, level - 1); + //startMipRangeRequest(level - 1, level - 1); + startRequestForNextMipLevel(); }); timer->setSingleShot(true); timer->setInterval(6000); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index c032a9c29d..b11ae687a2 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -41,7 +41,7 @@ public: }; /// A texture loaded from the network. -class NetworkTexture : public Resource, public Texture { +class NetworkTexture : public Resource, public Texture, public gpu::Texture::MipInterestListener { Q_OBJECT public: @@ -57,6 +57,9 @@ public: gpu::TexturePointer getFallbackTexture() const; + void handleMipInterestCallback(uint16_t level) override; + Q_INVOKABLE void handleMipInterestLevel(uint16_t level); + signals: void networkTextureCreated(const QWeakPointer& self); @@ -77,6 +80,8 @@ protected: Q_INVOKABLE void loadContent(const QByteArray& content); Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight); + void startRequestForNextMipLevel(); + void startMipRangeRequest(uint16_t low, uint16_t high); void maybeCreateKTX(); @@ -92,9 +97,7 @@ private: DONE_LOADING }; - bool _initialKtxLoaded { false }; - //KTXLoadState _ktxLoadState; KTXFilePointer _file; static const uint16_t NULL_MIP_LEVEL; bool _sourceIsKTX { false }; @@ -102,6 +105,8 @@ private: std::pair _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL }; ResourceRequest* _ktxHeaderRequest { nullptr }; ResourceRequest* _ktxMipRequest { nullptr }; + uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL }; + uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL }; QByteArray _ktxHeaderData; QByteArray _ktxHighMipData;