diff --git a/libraries/model-networking/CMakeLists.txt b/libraries/model-networking/CMakeLists.txt index ed8cd7b5f9..00aa17ff57 100644 --- a/libraries/model-networking/CMakeLists.txt +++ b/libraries/model-networking/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME model-networking) setup_hifi_library() -link_hifi_libraries(shared networking model fbx) +link_hifi_libraries(shared networking model fbx ktx) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 1f21e9e78d..4224cf076c 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -18,27 +18,39 @@ #include #include #include + +#if DEBUG_DUMP_TEXTURE_LOADS #include #include +#endif #include #include #include +#include + #include #include #include #include +#include #include "ModelNetworkingLogging.h" #include #include Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image") +Q_LOGGING_CATEGORY(trace_resource_parse_ktx, "trace.resource.parse.ktx") +Q_LOGGING_CATEGORY(trace_resource_cache_ktx, "trace.resource.cache.ktx") -TextureCache::TextureCache() { +const std::string TextureCache::KTX_DIRNAME { "ktx_cache" }; +const std::string TextureCache::KTX_EXT { "ktx" }; + +TextureCache::TextureCache() : + _ktxCache(KTX_DIRNAME, KTX_EXT) { setUnusedResourceCacheSize(0); setObjectName("TextureCache"); @@ -61,7 +73,7 @@ TextureCache::~TextureCache() { // this list taken from Ken Perlin's Improved Noise reference implementation (orig. in Java) at // http://mrl.nyu.edu/~perlin/noise/ -const int permutation[256] = +const int permutation[256] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, @@ -241,7 +253,7 @@ NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type t return NetworkTexture::TextureLoaderFunc(); break; } - + case Type::DEFAULT_TEXTURE: default: { return model::TextureUsage::create2DTextureFromImage; @@ -259,12 +271,32 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, Type type QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) { + KTXFilePointer file = _ktxCache.getFile(url); const TextureExtra* textureExtra = static_cast(extra); auto type = textureExtra ? textureExtra->type : Type::DEFAULT_TEXTURE; - auto content = textureExtra ? textureExtra->content : QByteArray(); - auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS; - return QSharedPointer(new NetworkTexture(url, type, content, maxNumPixels), - &Resource::deleter); + + NetworkTexture* texture; + if (file) { + texture = new NetworkTexture(url, type, file); + } else { + auto content = textureExtra ? textureExtra->content : QByteArray(); + auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS; + texture = new NetworkTexture(url, type, content, maxNumPixels); + } + + return QSharedPointer(texture, &Resource::deleter); +} + +NetworkTexture::NetworkTexture(const QUrl& url, Type type, const KTXFilePointer& file) : + Resource(url), + _type(type), + _file(file) { + _textureSource = std::make_shared(); + + if (file) { + _startedLoading = true; + QMetaObject::invokeMethod(this, "loadFile", Qt::QueuedConnection); + } } NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& content, int maxNumPixels) : @@ -278,7 +310,6 @@ NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& con _loaded = true; } - std::string theName = url.toString().toStdString(); // if we have content, load it after we have our self pointer if (!content.isEmpty()) { _startedLoading = true; @@ -299,149 +330,6 @@ NetworkTexture::TextureLoaderFunc NetworkTexture::getTextureLoader() const { return getTextureLoaderForType(_type); } - -class ImageReader : public QRunnable { -public: - - ImageReader(const QWeakPointer& resource, const QByteArray& data, - const QUrl& url = QUrl(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); - - virtual void run() override; - -private: - static void listSupportedImageFormats(); - - QWeakPointer _resource; - QUrl _url; - QByteArray _content; - int _maxNumPixels; -}; - -void NetworkTexture::downloadFinished(const QByteArray& data) { - // send the reader off to the thread pool - QThreadPool::globalInstance()->start(new ImageReader(_self, data, _url)); -} - -void NetworkTexture::loadContent(const QByteArray& content) { - QThreadPool::globalInstance()->start(new ImageReader(_self, content, _url, _maxNumPixels)); -} - -ImageReader::ImageReader(const QWeakPointer& resource, const QByteArray& data, - const QUrl& url, int maxNumPixels) : - _resource(resource), - _url(url), - _content(data), - _maxNumPixels(maxNumPixels) -{ -#if DEBUG_DUMP_TEXTURE_LOADS - static auto start = usecTimestampNow() / USECS_PER_MSEC; - auto now = usecTimestampNow() / USECS_PER_MSEC - start; - QString urlStr = _url.toString(); - auto dot = urlStr.lastIndexOf("."); - QString outFileName = QString(QCryptographicHash::hash(urlStr.toLocal8Bit(), QCryptographicHash::Md5).toHex()) + urlStr.right(urlStr.length() - dot); - QFile loadRecord("h:/textures/loads.txt"); - loadRecord.open(QFile::Text | QFile::Append | QFile::ReadWrite); - loadRecord.write(QString("%1 %2\n").arg(now).arg(outFileName).toLocal8Bit()); - outFileName = "h:/textures/" + outFileName; - QFileInfo outInfo(outFileName); - if (!outInfo.exists()) { - QFile outFile(outFileName); - outFile.open(QFile::WriteOnly | QFile::Truncate); - outFile.write(data); - outFile.close(); - } -#endif - DependencyManager::get()->incrementStat("PendingProcessing"); -} - -void ImageReader::listSupportedImageFormats() { - static std::once_flag once; - std::call_once(once, []{ - auto supportedFormats = QImageReader::supportedImageFormats(); - qCDebug(modelnetworking) << "List of supported Image formats:" << supportedFormats.join(", "); - }); -} - -void ImageReader::run() { - DependencyManager::get()->decrementStat("PendingProcessing"); - - CounterStat counter("Processing"); - - PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } }); - auto originalPriority = QThread::currentThread()->priority(); - if (originalPriority == QThread::InheritPriority) { - originalPriority = QThread::NormalPriority; - } - QThread::currentThread()->setPriority(QThread::LowPriority); - Finally restorePriority([originalPriority]{ - QThread::currentThread()->setPriority(originalPriority); - }); - - if (!_resource.data()) { - qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; - return; - } - listSupportedImageFormats(); - - // Help the QImage loader by extracting the image file format from the url filename ext. - // Some tga are not created properly without it. - auto filename = _url.fileName().toStdString(); - auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); - QImage image = QImage::fromData(_content, filenameExtension.c_str()); - - // Note that QImage.format is the pixel format which is different from the "format" of the image file... - auto imageFormat = image.format(); - int imageWidth = image.width(); - int imageHeight = image.height(); - - if (imageWidth == 0 || imageHeight == 0 || imageFormat == QImage::Format_Invalid) { - if (filenameExtension.empty()) { - qCDebug(modelnetworking) << "QImage failed to create from content, no file extension:" << _url; - } else { - qCDebug(modelnetworking) << "QImage failed to create from content" << _url; - } - return; - } - - if (imageWidth * imageHeight > _maxNumPixels) { - float scaleFactor = sqrtf(_maxNumPixels / (float)(imageWidth * imageHeight)); - int originalWidth = imageWidth; - int originalHeight = imageHeight; - imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f); - imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f); - QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - image.swap(newImage); - qCDebug(modelnetworking) << "Downscale image" << _url - << "from" << originalWidth << "x" << originalHeight - << "to" << imageWidth << "x" << imageHeight; - } - - gpu::TexturePointer texture = nullptr; - { - // Double-check the resource still exists between long operations. - auto resource = _resource.toStrongRef(); - if (!resource) { - qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; - return; - } - - auto url = _url.toString().toStdString(); - - PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffffff00, 0); - texture.reset(resource.dynamicCast()->getTextureLoader()(image, url)); - } - - // Ensure the resource has not been deleted - auto resource = _resource.toStrongRef(); - if (!resource) { - qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; - } else { - QMetaObject::invokeMethod(resource.data(), "setImage", - Q_ARG(gpu::TexturePointer, texture), - Q_ARG(int, imageWidth), Q_ARG(int, imageHeight)); - } -} - void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight) { _originalWidth = originalWidth; @@ -464,3 +352,196 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth, emit networkTextureCreated(qWeakPointerCast (_self)); } + +class Reader : public QRunnable { +public: + Reader(const QWeakPointer& resource, const QUrl& url) : _resource(resource), _url(url) { + DependencyManager::get()->incrementStat("PendingProcessing"); + } + void run() override final { + DependencyManager::get()->decrementStat("PendingProcessing"); + CounterStat counter("Processing"); + + // Run this with low priority, then restore thread priority + auto originalPriority = QThread::currentThread()->priority(); + if (originalPriority == QThread::InheritPriority) { + originalPriority = QThread::NormalPriority; + } + QThread::currentThread()->setPriority(QThread::LowPriority); + Finally restorePriority([originalPriority]{ + QThread::currentThread()->setPriority(originalPriority); + }); + + if (!_resource.lock()) { // to ensure the resource is still needed + qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope"; + return; + } + + read(); + } + virtual void read() = 0; + +protected: + QWeakPointer _resource; + QUrl _url; +}; + +class FileReader : public Reader { +public: + FileReader(const QWeakPointer& resource, const QUrl& url) : Reader(resource, url) {} + void read() override final; +}; + +class ImageReader : public Reader { +public: + ImageReader(const QWeakPointer& resource, const QUrl& url, + const QByteArray& data, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); + void read() override final; + +private: + static void listSupportedImageFormats(); + + QByteArray _content; + int _maxNumPixels; +}; + +void NetworkTexture::downloadFinished(const QByteArray& data) { + // send the reader off to the thread pool + QThreadPool::globalInstance()->start(new ImageReader(_self, _url, data)); +} + +void NetworkTexture::loadFile() { + QThreadPool::globalInstance()->start(new FileReader(_self, _url)); +} + +void NetworkTexture::loadContent(const QByteArray& content) { + QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels)); +} + +ImageReader::ImageReader(const QWeakPointer& resource, const QUrl& url, + const QByteArray& data, int maxNumPixels) : + Reader(resource, url), _content(data), _maxNumPixels(maxNumPixels) { + listSupportedImageFormats(); +#if DEBUG_DUMP_TEXTURE_LOADS + static auto start = usecTimestampNow() / USECS_PER_MSEC; + auto now = usecTimestampNow() / USECS_PER_MSEC - start; + QString urlStr = _url.toString(); + auto dot = urlStr.lastIndexOf("."); + QString outFileName = QString(QCryptographicHash::hash(urlStr.toLocal8Bit(), QCryptographicHash::Md5).toHex()) + urlStr.right(urlStr.length() - dot); + QFile loadRecord("h:/textures/loads.txt"); + loadRecord.open(QFile::Text | QFile::Append | QFile::ReadWrite); + loadRecord.write(QString("%1 %2\n").arg(now).arg(outFileName).toLocal8Bit()); + outFileName = "h:/textures/" + outFileName; + QFileInfo outInfo(outFileName); + if (!outInfo.exists()) { + QFile outFile(outFileName); + outFile.open(QFile::WriteOnly | QFile::Truncate); + outFile.write(data); + outFile.close(); + } +#endif +} + +void ImageReader::listSupportedImageFormats() { + static std::once_flag once; + std::call_once(once, []{ + auto supportedFormats = QImageReader::supportedImageFormats(); + qCDebug(modelnetworking) << "List of supported Image formats:" << supportedFormats.join(", "); + }); +} + +void FileReader::read() { + PROFILE_RANGE_EX(resource_parse_ktx, __FUNCTION__, 0xffff0000, 0); + + // TODO: + // auto ktx = ktx::KTX::create(); + // auto texture = gpu::Texture::unserialize(getUsage(), getUsageType(), ktx, getSampler()); + // FIXME: do I need to set the file as a backing file here? +} + +void ImageReader::read() { + PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } }); + + // Help the QImage loader by extracting the image file format from the url filename ext. + // Some tga are not created properly without it. + auto filename = _url.fileName().toStdString(); + auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); + QImage image = QImage::fromData(_content, filenameExtension.c_str()); + int imageWidth = image.width(); + int imageHeight = image.height(); + + // Validate that the image loaded + if (imageWidth == 0 || imageHeight == 0 || image.format() == QImage::Format_Invalid) { + QString reason(filenameExtension.empty() ? "" : "(no file extension)"); + qCWarning(modelnetworking) << "Failed to load" << _url << reason; + return; + } + + // Validate the image is less than _maxNumPixels, and downscale if necessary + if (imageWidth * imageHeight > _maxNumPixels) { + float scaleFactor = sqrtf(_maxNumPixels / (float)(imageWidth * imageHeight)); + int originalWidth = imageWidth; + int originalHeight = imageHeight; + imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f); + imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f); + QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + image.swap(newImage); + qCDebug(modelnetworking).nospace() << "Downscaled " << _url << " (" << + QSize(originalWidth, originalHeight) << " to " << + QSize(imageWidth, imageHeight) << ")"; + } + + // Load the image into a gpu::Texture + gpu::TexturePointer texture = nullptr; + { + auto resource = _resource.lock(); // to ensure the resource is still needed + if (!resource) { + qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope"; + return; + } + + auto url = _url.toString().toStdString(); + + PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0); + texture.reset(resource.dynamicCast()->getTextureLoader()(image, url)); + texture->setSource(url); + } + + // Hash the source image to use as a filename for on-disk caching + std::string hash; + { + QCryptographicHash hasher(QCryptographicHash::Md5); + hasher.addData((const char*)image.bits(), image.byteCount() * sizeof(char)); + hash = hasher.result().toHex().toStdString(); + } + + { + auto resource = _resource.lock(); // to ensure the resource is still needed + if (!resource) { + qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope"; + return; + } + + PROFILE_RANGE_EX(resource_cache_ktx, __FUNCTION__, 0xffffff00, 0); + auto ktx = gpu::Texture::serialize(*texture); + const char* data = reinterpret_cast(ktx->_storage->data()); + size_t length = ktx->_storage->size(); + KTXFilePointer file; + auto& ktxCache = DependencyManager::get()->_ktxCache; + if (!ktx || !(file = ktxCache.writeFile({ _url, hash, data, length }))) { + qCWarning(modelnetworking) << _url << "file cache failed"; + } else { + resource.dynamicCast()->_file = file; + // FIXME: do I need to set the file as a backing file here? + } + } + + auto resource = _resource.toStrongRef(); // to ensure the resource is still needed + if (resource) { + QMetaObject::invokeMethod(resource.data(), "setImage", + Q_ARG(gpu::TexturePointer, texture), + Q_ARG(int, imageWidth), Q_ARG(int, imageHeight)); + } else { + qCDebug(modelnetworking) << _url << "loading stopped; resource out of scope"; + } +} diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 749b5a2ebb..f05ab6b220 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -23,6 +23,8 @@ #include #include +#include "KTXCache.h" + const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192; namespace gpu { @@ -63,6 +65,7 @@ public: typedef gpu::Texture* TextureLoader(const QImage& image, const std::string& srcImageName); using TextureLoaderFunc = std::function; + NetworkTexture(const QUrl& url, Type type, const KTXFilePointer& file); NetworkTexture(const QUrl& url, Type type, const QByteArray& content, int maxNumPixels); NetworkTexture(const QUrl& url, const TextureLoaderFunc& textureLoader, const QByteArray& content); @@ -80,17 +83,20 @@ signals: void networkTextureCreated(const QWeakPointer& self); protected: - virtual bool isCacheable() const override { return _loaded; } virtual void downloadFinished(const QByteArray& data) override; Q_INVOKABLE void loadContent(const QByteArray& content); + Q_INVOKABLE void loadFile(); Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight); private: + friend class ImageReader; + Type _type; TextureLoaderFunc _textureLoader { [](const QImage&, const std::string&){ return nullptr; } }; + KTXFilePointer _file; int _originalWidth { 0 }; int _originalHeight { 0 }; int _width { 0 }; @@ -143,9 +149,15 @@ protected: const void* extra) override; private: + friend class ImageReader; + friend class DilatableNetworkTexture; + TextureCache(); virtual ~TextureCache(); - friend class DilatableNetworkTexture; + + static const std::string KTX_DIRNAME; + static const std::string KTX_EXT; + KTXCache _ktxCache; gpu::TexturePointer _permutationNormalTexture; gpu::TexturePointer _whiteTexture; diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index ce602b7b9b..e8d6d4cd6f 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -85,87 +85,6 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) { return srcImage; } -gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bool write = true, bool read = true) { - if (!srcTexture) { - return nullptr; - } - - static QString ktxCacheFolder; - static std::once_flag once; - std::call_once(once, [&] { - // Prepare cache directory - static const QString HIFI_KTX_FOLDER("hifi_ktx"); - QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - ktxCacheFolder = docsLocation + "/" + HIFI_KTX_FOLDER + "/"; - QFileInfo info(ktxCacheFolder); - if (!info.exists()) { - QDir(docsLocation).mkpath(HIFI_KTX_FOLDER); - } - }); - - - std::string cleanedName = QCryptographicHash::hash(QUrl::toPercentEncoding(name.c_str()), QCryptographicHash::Sha1).toHex().toStdString(); - std::string cacheFilename(ktxCacheFolder.toStdString()); - cacheFilename += "/"; - cacheFilename += cleanedName; - cacheFilename += ".ktx"; - - gpu::Texture* returnedTexture = srcTexture; - { - if (write && !QFileInfo(cacheFilename.c_str()).exists()) { - auto ktxMemory = gpu::Texture::serialize(*srcTexture); - if (ktxMemory) { - const auto& ktxStorage = ktxMemory->getStorage(); - auto header = ktxMemory->getHeader(); - QFile outFile(cacheFilename.c_str()); - if (!outFile.open(QFile::Truncate | QFile::ReadWrite)) { - throw std::runtime_error("Unable to open file"); - } - //auto ktxSize = sizeof(ktx::Header); // ktxStorage->size() - auto ktxSize = ktxStorage->size(); - outFile.resize(ktxSize); - auto dest = outFile.map(0, ktxSize); - memcpy(dest, ktxStorage->data(), ktxSize); - outFile.unmap(dest); - outFile.close(); - } - } - - if (read && QFileInfo(cacheFilename.c_str()).exists()) { -#define DEBUG_KTX_LOADING 0 -#if DEBUG_KTX_LOADING - { - FILE* file = fopen(cacheFilename.c_str(), "rb"); - if (file != nullptr) { - // obtain file size: - fseek (file , 0 , SEEK_END); - auto size = ftell(file); - rewind(file); - - auto storage = std::make_shared(size); - fread(storage->data(), 1, storage->size(), file); - fclose (file); - - //then create a new texture out of the ktx - auto theNewTexure = Texture::unserialize(srcTexture->getUsage(), srcTexture->getUsageType(), - ktx::KTX::create(std::static_pointer_cast(storage)), srcTexture->getSampler()); - - if (theNewTexure) { - returnedTexture = theNewTexure; - delete srcTexture; - } - } - } -#else - ktx::StoragePointer storage = std::make_shared(cacheFilename.c_str()); - auto ktxFile = ktx::KTX::create(storage); - returnedTexture->setKtxBacking(ktxFile); -#endif - } - } - return returnedTexture; -} - void TextureMap::setTextureSource(TextureSourcePointer& textureSource) { _textureSource = textureSource; } @@ -355,7 +274,6 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag ::generateMips(theTexture, image, false); } theTexture->setSource(srcImageName); - theTexture = cacheTexture(theTexture->source(), theTexture); } return theTexture; @@ -405,7 +323,6 @@ gpu::Texture* TextureUsage::createNormalTextureFromNormalImage(const QImage& src generateMips(theTexture, image, true); theTexture->setSource(srcImageName); - theTexture = cacheTexture(theTexture->source(), theTexture, true, true); } return theTexture; @@ -496,7 +413,6 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm generateMips(theTexture, image, true); theTexture->setSource(srcImageName); - theTexture = cacheTexture(theTexture->source(), theTexture, true, false); } return theTexture; @@ -533,7 +449,6 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcIma generateMips(theTexture, image, true); theTexture->setSource(srcImageName); - theTexture = cacheTexture(theTexture->source(), theTexture); } return theTexture; @@ -574,7 +489,6 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& s generateMips(theTexture, image, true); theTexture->setSource(srcImageName); - theTexture = cacheTexture(theTexture->source(), theTexture); } return theTexture; @@ -612,7 +526,6 @@ gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImag generateMips(theTexture, image, true); theTexture->setSource(srcImageName); - theTexture = cacheTexture(theTexture->source(), theTexture); } return theTexture; @@ -946,7 +859,6 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm } theTexture->setSource(srcImageName); - theTexture = cacheTexture(theTexture->source(), theTexture); } }