diff options
author | Määttä Antti <antti.maatta@qt.io> | 2019-02-05 15:31:46 +0200 |
---|---|---|
committer | Antti Määttä <antti.maatta@qt.io> | 2019-03-12 12:11:08 +0000 |
commit | db433e6b7b543a54865c770fd4ca15160e44b2c7 (patch) | |
tree | 68fb6c56f7d6b2fee91975733ee94c06698eeae0 /src | |
parent | 63ec9f17ed1cf4da27015244f97bb161e51949ce (diff) |
Implement delayed image loading and slide resource list
Task-number: QT3DS-2664
Change-Id: I5310329fbf6e4e07cade28ae68161081ddf77f3c
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Tomi Korpipää <tomi.korpipaa@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/runtime/q3dsengine.cpp | 44 | ||||
-rw-r--r-- | src/runtime/q3dsengine_p.h | 65 | ||||
-rw-r--r-- | src/runtime/q3dsimagemanager.cpp | 378 | ||||
-rw-r--r-- | src/runtime/q3dsimagemanager_p.h | 108 | ||||
-rw-r--r-- | src/runtime/q3dsprofiler.cpp | 16 | ||||
-rw-r--r-- | src/runtime/q3dsprofiler_p.h | 1 | ||||
-rw-r--r-- | src/runtime/q3dsscenemanager.cpp | 638 | ||||
-rw-r--r-- | src/runtime/q3dsscenemanager_p.h | 16 | ||||
-rw-r--r-- | src/runtime/q3dsslideplayer.cpp | 36 | ||||
-rw-r--r-- | src/runtime/q3dsuippresentation.cpp | 48 | ||||
-rw-r--r-- | src/runtime/q3dsuippresentation_p.h | 39 | ||||
-rw-r--r-- | src/runtime/slideplayerng/q3dsslideplayerng.cpp | 35 | ||||
-rw-r--r-- | src/runtime/slideplayerng/q3dsslideplayerng_p.h | 1 |
13 files changed, 1030 insertions, 395 deletions
diff --git a/src/runtime/q3dsengine.cpp b/src/runtime/q3dsengine.cpp index 005ad7e..0ac262c 100644 --- a/src/runtime/q3dsengine.cpp +++ b/src/runtime/q3dsengine.cpp @@ -146,6 +146,27 @@ static void q3ds_msg_handler(QtMsgType type, const QMessageLogContext &ctx, cons } } +void Q3DSEngine::SlideResourceCounter::print() +{ + static const bool debugging = qEnvironmentVariableIntValue("Q3DS_DEBUG") >= 1; + if (debugging) { + qDebug()<< "SlideResourceCounter resources:"; + const auto keys = counters.keys(); + for (auto &x : keys) + qDebug()<< x << ": " << counters[x]; + if (createSet.size()) { + qDebug() << "New resources: "; + for (auto y : qAsConst(createSet)) + qDebug() << y; + } + if (deleteSet.size()) { + qDebug() << "Deleted resources: "; + for (auto y : qAsConst(deleteSet)) + qDebug() << y; + } + } +} + Q3DSEngine::Q3DSEngine() { initResources(); @@ -920,6 +941,21 @@ void Q3DSEngine::finishAsyncLoad(bool wait) } } +void Q3DSEngine::loadSlideResources(Q3DSSlide *slide, Q3DSUipPresentation *presentation) +{ + m_resourceReferenceCounter.handleLoadSlide(slide, presentation); + Q3DSImageManager::instance().beginImageLoad(m_resourceReferenceCounter.createSet); +} + +void Q3DSEngine::unloadSlideResources(Q3DSSlide *slide, Q3DSUipPresentation *presentation) +{ + slide->setUnloadSlide(true); + if (!slide->active()) { + m_resourceReferenceCounter.handleUnloadSlide(slide, presentation); + Q3DSImageManager::instance().beginUnload(m_resourceReferenceCounter.deleteSet); + } +} + bool Q3DSEngine::loadSubUipPresentation(UipPresentation *pres) { Q_ASSERT(pres); @@ -1848,16 +1884,12 @@ void Q3DSEngine::goToSlideByDirection(Q3DSGraphObject *context, Q3DSUipPresentat void Q3DSEngine::preloadSlide(Q3DSSlide *slide, Q3DSUipPresentation *presentation) { - Q_UNUSED(slide); - Q_UNUSED(presentation); - // TODO: Implementation + loadSlideResources(slide, presentation); } void Q3DSEngine::unloadSlide(Q3DSSlide *slide, Q3DSUipPresentation *presentation) { - Q_UNUSED(slide); - Q_UNUSED(presentation); - // TODO: Implementation + unloadSlideResources(slide, presentation); } void Q3DSEngine::loadBehaviorInstance(Q3DSBehaviorInstance *behaviorInstance, diff --git a/src/runtime/q3dsengine_p.h b/src/runtime/q3dsengine_p.h index ab85fc2..baf058b 100644 --- a/src/runtime/q3dsengine_p.h +++ b/src/runtime/q3dsengine_p.h @@ -270,7 +270,8 @@ public: } void finishAsyncLoad(bool wait = true); - + void loadSlideResources(Q3DSSlide *slide, Q3DSUipPresentation *presentation); + void unloadSlideResources(Q3DSSlide *slide, Q3DSUipPresentation *presentation); void createAspectEngine(); public Q_SLOTS: @@ -335,6 +336,67 @@ private: void finishAsyncLoadForSubpresentation(const QString &name); void reparentScene(SceneLoaderAsync *loader); + struct SlideResourceCounter + { + QHash<QUrl, int> counters; + QSet<QUrl> createSet; + QSet<QUrl> deleteSet; + + QVector<Q3DSSlide *> loadedSlides; + + void increment(const QSet<QUrl> &set) + { + for (auto &r : set) { + if (counters.value(r, 0) == 0) + createSet.insert(r); + counters[r]++; + } + } + void decrement(const QSet<QUrl> &set) + { + for (auto &r : set) { + if (counters.contains(r)) { + int count = qMax(counters[r] - 1, 0); + if (count == 0) + deleteSet.insert(r); + counters[r] = count; + } + } + } + void begin() + { + createSet.clear(); + deleteSet.clear(); + } + void reset() + { + loadedSlides.clear(); + counters.clear(); + begin(); + } + void handleLoadSlide(Q3DSSlide *slide, Q3DSUipPresentation *presentation) + { + if (loadedSlides.contains(slide)) + return; + slide->generateResourceSet(presentation); + loadedSlides.push_back(slide); + begin(); + increment(slide->resourceSet()); + print(); + } + void handleUnloadSlide(Q3DSSlide *slide, Q3DSUipPresentation *presentation) + { + if (!loadedSlides.contains(slide)) + return; + slide->generateResourceSet(presentation); + loadedSlides.removeOne(slide); + begin(); + decrement(slide->resourceSet()); + print(); + } + void print(); + }; + bool loadPresentations(); void finalizePresentations(); bool loadUipPresentation(UipPresentation *pres); @@ -405,6 +467,7 @@ private: int m_quickDeltaCount = 0; QVector<SceneLoaderAsync *> m_asyncSceneLoaders; + SlideResourceCounter m_resourceReferenceCounter; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Q3DSEngine::Flags) diff --git a/src/runtime/q3dsimagemanager.cpp b/src/runtime/q3dsimagemanager.cpp index f0eba05..5168e8a 100644 --- a/src/runtime/q3dsimagemanager.cpp +++ b/src/runtime/q3dsimagemanager.cpp @@ -45,6 +45,18 @@ QT_BEGIN_NAMESPACE QMutex Q3DSImageManager::s_loadMutex; +static bool isAsyncImageLoadingEnabled() +{ + static bool enabled = (qEnvironmentVariableIntValue("Q3DS_ASYNC_LOADING") & 1) > 0; + return enabled; +} + +static bool isDelayedImageLoadingEnabled() +{ + static bool enabled = (qEnvironmentVariableIntValue("Q3DS_ASYNC_LOADING") & 2) > 0; + return enabled; +} + Q3DSImageManager::Q3DSImageManager() { m_threadPool.setMaxThreadCount(qMax(2, QThread::idealThreadCount() - 2)); @@ -62,13 +74,18 @@ void Q3DSImageManager::invalidate() m_cache.clear(); m_ioTime = 0; m_iblTime = 0; + m_resourceSet.clear(); + m_loadImageDataAsync.clear(); + qDeleteAll(m_pendingSetSource); + m_pendingSetSource.clear(); + m_reloadableTextures.clear(); } Qt3DRender::QAbstractTexture *Q3DSImageManager::newTextureForImage(Qt3DCore::QEntity *parent, ImageFlags flags, const QByteArray &id, Q3DSProfiler *profiler, - const char *profDesc, ...) + const QString &profilerInfo) { auto tex = new Qt3DRender::QTexture2D(parent); @@ -76,14 +93,87 @@ Qt3DRender::QAbstractTexture *Q3DSImageManager::newTextureForImage(Qt3DCore::QEn info.flags = flags; m_metadata.insert(tex, info); + if (profiler && !profilerInfo.isNull()) + profiler->trackNewObject(tex, Q3DSProfiler::Texture2DObject, id, profilerInfo); + + return tex; +} + +ReloadableTexturePtr Q3DSImageManager::newReloadableTextureForImage(Qt3DCore::QEntity *parent, + ImageFlags flags, + const QByteArray &id, + Q3DSProfiler *profiler, + const char *profDesc, ...) +{ + ReloadableTexturePtr texture; + QString info; if (profiler && profDesc) { va_list ap; va_start(ap, profDesc); - profiler->vtrackNewObject(tex, Q3DSProfiler::Texture2DObject, id, profDesc, ap); + info = QString::vasprintf(profDesc, ap); va_end(ap); } + texture.reset(new ReloadableTexture(parent, flags & GenerateMipMapsForIBL, id, info, profiler)); + m_reloadableTextures.push_back(texture); + return texture; +} - return tex; +void ReloadableTexture::setSource(const QUrl &source) +{ + if (m_source.isEmpty() || m_source != source) { + m_source = source; + m_loaded = false; + triggerLoading(); + } +} + +void ReloadableTexture::triggerLoading() +{ + if (!m_source.isEmpty() && m_loadFunc && m_unloadFunc) + reload(); +} + +void ReloadableTexture::reload() +{ + if (m_loaded) + return; + if (isDelayedImageLoadingEnabled() && !Q3DSImageManager::instance().inResourceSet(m_source)) + return; + Q3DSImageManager::ImageFlags flags; + if (m_generateIBL) + flags = Q3DSImageManager::GenerateMipMapsForIBL; + if (!m_texture) { + m_texture = Q3DSImageManager::instance().newTextureForImage(m_parent, flags, m_id, + m_profiler, m_profileInfo); + } + Q3DSImageManager::instance().loadImageData(m_source, true); + Q3DSImageManager::instance().setSource(m_texture, m_source); +} + +void ReloadableTexture::unload() +{ + if (m_texture) { + m_unloadFunc(); + + const auto texImages = m_texture->textureImages(); + for (Qt3DRender::QAbstractTextureImage *oldImage : texImages) { + m_texture->removeTextureImage(oldImage); + delete oldImage; + } + Qt3DCore::QNode *nullParent = nullptr; + m_texture->setParent(nullParent); + delete m_texture; + m_texture = nullptr; + m_loaded = false; + } +} + +void ReloadableTexture::loaded(Qt3DRender::QAbstractTexture *texture) +{ + if (m_texture == texture) { + m_loaded = true; + m_loadFunc(); + } } class Q3DSTextureImageDataGen : public Qt3DRender::QTextureImageDataGenerator @@ -129,20 +219,18 @@ private: Qt3DRender::QTextureImageDataGeneratorPtr m_gen; }; -QVector<Qt3DRender::QTextureImageDataPtr> Q3DSImageManager::load(const QUrl &source, ImageFlags flags, bool *wasCached) +QVector<Qt3DRender::QTextureImageDataPtr> Q3DSImageManager::load(const QUrl &source, + ImageFlags flags) { const QString sourceStr = source.toLocalFile(); s_loadMutex.lock(); auto it = m_cache.constFind(sourceStr); if (it != m_cache.constEnd()) { - *wasCached = true; s_loadMutex.unlock(); return *it; } s_loadMutex.unlock(); - *wasCached = false; - QElapsedTimer t; t.start(); qCDebug(lcScene, "Loading image %s", qPrintable(sourceStr)); @@ -260,155 +348,211 @@ QVector<Qt3DRender::QTextureImageDataPtr> Q3DSImageManager::load(const QUrl &sou void Q3DSImageManager::finishAsyncLoad(bool wait) { - if (m_setSourceAsync.isEmpty()) + QMutexLocker lock(&m_finishAsyncLoadLock); + if (m_loadImageDataAsync.isEmpty() && m_pendingSetSource.isEmpty()) return; if (wait) { + lock.unlock(); QElapsedTimer t; t.start(); { QFutureSynchronizer<void> sync; - for (auto &item : qAsConst(m_setSourceAsync)) + for (auto &item : qAsConst(m_loadImageDataAsync)) sync.addFuture(item.future); } qCDebug(lcPerf, "Finish async image loading took %lld ms", t.elapsed()); - } - bool allDone = true; - for (int i = 0; i < m_setSourceAsync.size(); ++i) { - auto &item = m_setSourceAsync[i]; - if (!item.done) { - if (item.future.isFinished() && item.tex->thread() == QThread::currentThread()) { + for (auto &item : qAsConst(m_loadImageDataAsync)) { + if (m_pendingSetSource.contains(item.source)) { + const auto *pending = m_pendingSetSource[item.source]; + for (const auto &tex : qAsConst(*pending)) + setSource(tex, QUrl::fromLocalFile(item.source)); + delete pending; + m_pendingSetSource.remove(item.source); + } + } + m_pendingSetSource.clear(); + } else { + lock.relock(); + QThread *thread = QThread::currentThread(); + for (auto &item : m_loadImageDataAsync) { + if (!item.done && item.future.isFinished()) { item.done = true; - setSource(item.tex, item.source, item.preferKtx, false); - } else { - allDone = false; + if (m_pendingSetSource.contains(item.source)) { + const auto pending = *m_pendingSetSource[item.source]; + for (const auto &tex : pending) { + if (tex->thread() == thread) { + setSource(tex, QUrl::fromLocalFile(item.source)); + m_pendingSetSource[item.source]->removeOne(tex); + } + } + if (m_pendingSetSource[item.source]->empty()) { + delete m_pendingSetSource[item.source]; + m_pendingSetSource.remove(item.source); + } + } } } } - if (allDone) { - m_setSourceAsync.clear(); - m_loadImageAsync.clear(); +} + +void Q3DSImageManager::beginImageLoad(const QSet<QUrl> &imageSet) +{ + m_resourceSet.unite(imageSet); + for (auto url : imageSet) { + QFileInfo info(url.toLocalFile()); + if (info.exists()) { + if (!m_loadImageDataAsync.contains(url.toLocalFile())) + loadImageData(url, true); + for (int i = 0; i < m_reloadableTextures.size(); ++i) { + if (m_reloadableTextures[i]->source() == url) + m_reloadableTextures[i]->reload(); + } + } } } -static bool isAsyncImageLoadingEnabled() +bool Q3DSImageManager::inResourceSet(const QUrl &url) const { - static bool enabled = (qEnvironmentVariableIntValue("Q3DS_ASYNC_LOADING") & 1) > 0; - return enabled; + return m_resourceSet.contains(url); } -void Q3DSImageManager::setSource(Qt3DRender::QAbstractTexture *tex, const QUrl &source, - bool preferKtx, bool async) +void Q3DSImageManager::beginUnload(const QSet<QUrl> &imageSet) +{ + m_resourceSet.subtract(imageSet); + if (!isDelayedImageLoadingEnabled()) + return; + for (auto url : imageSet) { + for (int i = 0; i < m_reloadableTextures.size(); ++i) { + if (m_reloadableTextures[i]->source() == url) { + m_metadata.remove(m_reloadableTextures[i]->texture()); + m_reloadableTextures[i]->unload(); + } + } + const QString name = url.toLocalFile(); + m_cache.remove(name); + if (m_loadImageDataAsync.contains(name) && m_loadImageDataAsync[name].done) + m_loadImageDataAsync.remove(name); + } +} + +void Q3DSImageManager::loadImageData(const QUrl &source, bool async) { TextureInfo info; - async = async && isAsyncImageLoadingEnabled(); + auto loadImage = [this](const TextureInfo &info, const QUrl &source) { + QVector<Qt3DRender::QTextureImageDataPtr> imageData; + // The generator (invoked from some Qt3D job thread later on) will just return the already + // loaded data. + imageData = load(source, info.flags); + }; + + QMutexLocker lock(&s_loadMutex); + if (m_cache.contains(source.toLocalFile())) + return; + lock.unlock(); + + const QString src = source.toLocalFile(); + if (async && isAsyncImageLoadingEnabled()) { + if (!m_loadImageDataAsync.contains(src)) { + qCDebug(lcScene, "Load image data async %s", qPrintable(src)); + LoadImageDataAsync item; + item.source = src; + item.future = QtConcurrent::run(&m_threadPool, loadImage, info, source); + m_loadImageDataAsync[src] = item; + } + } else { + loadImage(info, source); + } +} + +void Q3DSImageManager::setSource(Qt3DRender::QAbstractTexture *tex, const QUrl &source) +{ + TextureInfo info; + QString src = source.toLocalFile(); + s_loadMutex.lock(); auto it = m_metadata.find(tex); if (it != m_metadata.end()) { - if (it->source == source) { + if (it->source == src) { s_loadMutex.unlock(); return; } info = *it; } - s_loadMutex.unlock(); - - auto loadImageData = [this](TextureInfo info, Qt3DRender::QAbstractTexture *tex, - const QUrl &source, bool preferKtx, bool async) { - QVector<Qt3DRender::QTextureImageDataPtr> imageData; - if (!preferKtx) { - info.source = source; - // yes, it's all synchronous and this is intentional. The generator - // (invoked from some Qt3D job thread later on) will just return the already - // loaded data. - imageData = load(source, info.flags, &info.wasCached); - } else { - QString ktxSource = source.toLocalFile(); - ktxSource = ktxSource.left(ktxSource.lastIndexOf(QLatin1Char('.'))); - ktxSource.append(QLatin1String(".ktx")); - info.source = QUrl::fromLocalFile(ktxSource); - imageData = load(info.source, info.flags, &info.wasCached); - // If ktx is not found, load with the original extension - if (imageData.isEmpty()) { - qCWarning(lcPerf, "You have specified \"Use ktx texture if available\" option, " - "but did not provide a ktx texture: %s", qPrintable(ktxSource)); - info.source = source; - load(source, info.flags, &info.wasCached); - } - } - - // The rest will be done when the asynchronous loading has finished by calling this - // function with async = false. - if (async) - return; + if (!m_cache.contains(src)) { + if (!m_pendingSetSource.contains(src)) + m_pendingSetSource[src] = new QVector<Qt3DRender::QAbstractTexture *>(); + m_pendingSetSource[src]->push_back(tex); + s_loadMutex.unlock(); + return; + } + else { + info.wasCached = true; + } + s_loadMutex.unlock(); - const auto texImages = tex->textureImages(); - for (Qt3DRender::QAbstractTextureImage *oldImage : texImages) { - tex->removeTextureImage(oldImage); - delete oldImage; - } + auto &imageData = m_cache[src]; - if (!imageData.isEmpty()) { - info.size = QSize(imageData[0]->width(), imageData[0]->height()); - info.format = Qt3DRender::QAbstractTexture::TextureFormat(imageData[0]->format()); - s_loadMutex.lock(); - m_metadata.insert(tex, info); - s_loadMutex.unlock(); + const auto texImages = tex->textureImages(); + for (Qt3DRender::QAbstractTextureImage *oldImage : texImages) { + tex->removeTextureImage(oldImage); + delete oldImage; + } - // Mipmaps are used in three cases: in IBL images (with our own custom - // mipmap images), when the source provides mipmaps (e.g. a .ktx file - // with mipmaps in it), or when the custom property metadata (custom - // materials/effects) says so (mipmaps autogenerated in this case). If - // none of these holds, linear filtering is used. - - tex->setMagnificationFilter(Qt3DRender::QAbstractTexture::Linear); - tex->setGenerateMipMaps(false); // autogen is only for custom props and that's handled elsewhere - - if (imageData.count() > 1) { - tex->setMinificationFilter(Qt3DRender::QAbstractTexture::LinearMipMapLinear); - if (!info.wasCached) - qCDebug(lcScene, "%s provided mipmaps, mipmap filtering enabled", qPrintable(source.toLocalFile())); - } else { - tex->setMinificationFilter(Qt3DRender::QAbstractTexture::Linear); + if (!imageData.isEmpty()) { + info.size = QSize(imageData[0]->width(), imageData[0]->height()); + info.format = Qt3DRender::QAbstractTexture::TextureFormat(imageData[0]->format()); + s_loadMutex.lock(); + m_metadata.insert(tex, info); + s_loadMutex.unlock(); + // Mipmaps are used in three cases: in IBL images (with our own custom + // mipmap images), when the source provides mipmaps (e.g. a .ktx file + // with mipmaps in it), or when the custom property metadata (custom + // materials/effects) says so (mipmaps autogenerated in this case). If + // none of these holds, linear filtering is used. + + tex->setMagnificationFilter(Qt3DRender::QAbstractTexture::Linear); + // autogen is only for custom props and that's handled elsewhere + tex->setGenerateMipMaps(false); + + if (imageData.count() > 1) { + tex->setMinificationFilter(Qt3DRender::QAbstractTexture::LinearMipMapLinear); + if (!info.wasCached) { + qCDebug(lcScene, "%s provided mipmaps, mipmap filtering enabled", + qPrintable(source.toLocalFile())); } - - for (int i = 0; i < imageData.count(); ++i) - tex->addTextureImage(new TextureImage(source, i, imageData[i])); } else { - // Provide a dummy image when failing to load since we want to see - // something that makes it obvious a texture source file was missing. - info.size = QSize(64, 64); - info.format = Qt3DRender::QAbstractTexture::RGBA8_UNorm; - s_loadMutex.lock(); - m_metadata.insert(tex, info); - s_loadMutex.unlock(); - - QImage dummy(info.size, QImage::Format_ARGB32); - dummy.fill(Qt::magenta); - auto dummyData = Qt3DRender::QTextureImageDataPtr::create(); - dummyData->setImage(dummy); - - tex->addTextureImage(new TextureImage(source, 0, dummyData)); - qWarning("Using placeholder texture in place of %s", qPrintable(source.toLocalFile())); + tex->setMinificationFilter(Qt3DRender::QAbstractTexture::Linear); } - }; - const QString src = source.toLocalFile(); - if (async) { - m_setSourceAsync.append(TextureImageAsyncLoad()); - auto &item = m_setSourceAsync.last(); - item.tex = tex; - item.source = source; - item.preferKtx = preferKtx; - if (!m_loadImageAsync.contains(src)) { - qCDebug(lcScene, "Load image data async %s", qPrintable(src)); - m_loadImageAsync[src] = true; - item.future = QtConcurrent::run(&m_threadPool, loadImageData, info, tex, - source, preferKtx, true); - } + for (int i = 0; i < imageData.count(); ++i) + tex->addTextureImage(new TextureImage(source, i, imageData[i])); } else { - loadImageData(info, tex, source, preferKtx, false); + // Provide a dummy image when failing to load since we want to see + // something that makes it obvious a texture source file was missing. + info.size = QSize(64, 64); + info.format = Qt3DRender::QAbstractTexture::RGBA8_UNorm; + s_loadMutex.lock(); + m_metadata.insert(tex, info); + s_loadMutex.unlock(); + + QImage dummy(info.size, QImage::Format_ARGB32); + dummy.fill(Qt::magenta); + auto dummyData = Qt3DRender::QTextureImageDataPtr::create(); + dummyData->setImage(dummy); + + tex->addTextureImage(new TextureImage(source, 0, dummyData)); + qWarning("Using placeholder texture in place of %s", qPrintable(source.toLocalFile())); + } + textureLoaded(tex, source); +} + +void Q3DSImageManager::textureLoaded(Qt3DRender::QAbstractTexture *tex, const QUrl &source) +{ + for (auto &reloadable : m_reloadableTextures) { + if (reloadable->source() == source) + reloadable->loaded(tex); } } diff --git a/src/runtime/q3dsimagemanager_p.h b/src/runtime/q3dsimagemanager_p.h index 765e01e..2541224 100644 --- a/src/runtime/q3dsimagemanager_p.h +++ b/src/runtime/q3dsimagemanager_p.h @@ -50,6 +50,7 @@ #include <QThreadPool> #include <Qt3DRender/QAbstractTexture> #include <Qt3DRender/QTextureImageData> +#include <Qt3DRender/QParameter> QT_BEGIN_NAMESPACE @@ -59,6 +60,81 @@ namespace Qt3DCore { class QEntity; } +class ReloadableTexture +{ +public: + ReloadableTexture(Qt3DCore::QEntity *parent, bool generateIBL, const QByteArray &id, + const QString &profileInfo, Q3DSProfiler *profiler) + : m_parent(parent), m_id(id), m_profileInfo(profileInfo), m_generateIBL(generateIBL), + m_profiler(profiler) + { + + } + void setSource(const QUrl &source); + + template <typename Caller, typename... Params> + void onLoad(Caller caller, Params... params) + { + m_loadFunc = [this, caller, params...]() { + caller(m_texture, params...); + }; + triggerLoading(); + } + + template <typename Caller, typename... Params> + void onUnload(Caller caller, Params... params) + { + m_unloadFunc = [this, caller, params...]() { + caller(m_texture, params...); + }; + triggerLoading(); + } + + void reload(); + void unload(); + + QUrl source() const + { + return m_source; + } + void loaded(Qt3DRender::QAbstractTexture *texture); + bool wasLoaded() const + { + return m_loaded; + } + Qt3DRender::QAbstractTexture *texture() + { + return m_texture; + } + + QString info() + { + return m_profileInfo; + } + + Q3DSProfiler *profiler() + { + return m_profiler; + } + +private: + void triggerLoading(); + + Qt3DCore::QEntity *m_parent = nullptr; + Qt3DRender::QAbstractTexture *m_texture = nullptr; + QByteArray m_id; + QString m_profileInfo; + QUrl m_source; + bool m_generateIBL = false; + bool m_loaded = false; + Q3DSProfiler *m_profiler = nullptr; + + std::function<void()> m_loadFunc = nullptr; + std::function<void()> m_unloadFunc = nullptr; +}; + +typedef QSharedPointer<ReloadableTexture> ReloadableTexturePtr; + class Q3DSImageManager { public: @@ -76,9 +152,13 @@ public: ImageFlags flags, const QByteArray &id, Q3DSProfiler *profiler = nullptr, - const char *profName = nullptr, ...); - void setSource(Qt3DRender::QAbstractTexture *tex, const QUrl &source, bool preferKtx, - bool async = true); + const QString &profilerInfo = {}); + ReloadableTexturePtr newReloadableTextureForImage(Qt3DCore::QEntity *parent, + ImageFlags flags, + const QByteArray &id, + Q3DSProfiler *profiler = nullptr, + const char *profName = nullptr, ...); + void setSource(Qt3DRender::QAbstractTexture *tex, const QImage &image); QSize size(Qt3DRender::QAbstractTexture *tex) const; @@ -89,14 +169,21 @@ public: qint64 iblTimeMsecs() const { return m_iblTime; } void finishAsyncLoad(bool wait = true); + void beginImageLoad(const QSet<QUrl> &imageSet); + void beginUnload(const QSet<QUrl> &imageSet); + bool inResourceSet(const QUrl &url) const; private: - QVector<Qt3DRender::QTextureImageDataPtr> load(const QUrl &source, ImageFlags flags, bool *wasCached); + void loadImageData(const QUrl &source, bool async = true); + void setSource(Qt3DRender::QAbstractTexture *tex, const QUrl &source); + void textureLoaded(Qt3DRender::QAbstractTexture *tex, const QUrl &source); + QVector<Qt3DRender::QTextureImageDataPtr> load(const QUrl &source, ImageFlags flags); int blockSizeForFormat(QOpenGLTexture::TextureFormat format); QByteArray generateIblMip(int w, int h, int prevW, int prevH, QOpenGLTexture::TextureFormat format, int blockSize, const QByteArray &prevLevelData); + struct TextureInfo { ImageFlags flags; QUrl source; @@ -105,23 +192,26 @@ private: bool wasCached = false; }; - struct TextureImageAsyncLoad + struct LoadImageDataAsync { QFuture<void> future; - Qt3DRender::QAbstractTexture *tex = nullptr; - QUrl source; + QString source; bool preferKtx = false; bool done = false; }; + QVector<ReloadableTexturePtr> m_reloadableTextures; + QSet<QUrl> m_resourceSet; QHash<Qt3DRender::QAbstractTexture *, TextureInfo> m_metadata; QHash<QString, QVector<Qt3DRender::QTextureImageDataPtr> > m_cache; - QHash<QString, bool> m_loadImageAsync; - QVector<TextureImageAsyncLoad> m_setSourceAsync; + QHash<QString, LoadImageDataAsync> m_loadImageDataAsync; + QHash<QString, QVector<Qt3DRender::QAbstractTexture *> *> m_pendingSetSource; QThreadPool m_threadPool; static QMutex s_loadMutex; qint64 m_ioTime = 0; qint64 m_iblTime = 0; + QMutex m_finishAsyncLoadLock; + friend ReloadableTexture; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Q3DSImageManager::ImageFlags) diff --git a/src/runtime/q3dsprofiler.cpp b/src/runtime/q3dsprofiler.cpp index 727229a..1a02084 100644 --- a/src/runtime/q3dsprofiler.cpp +++ b/src/runtime/q3dsprofiler.cpp @@ -121,6 +121,22 @@ void Q3DSProfiler::trackNewObject(QObject *obj, ObjectType type, const QByteArra va_end(ap); } +void Q3DSProfiler::trackNewObject(QObject *obj, ObjectType type, const QByteArray &id, + const QString &info) +{ + if (!m_enabled) + return; + + ObjectData objd(obj, type, id); + objd.info = info; + m_objectData.insert(type, objd); + + m_objectDestroyConnections.append(QObject::connect(obj, &QObject::destroyed, + [this, obj, type, id]() { + m_objectData.remove(type, ObjectData(obj, type, id)); + })); +} + void Q3DSProfiler::vtrackNewObject(QObject *obj, ObjectType type, const QByteArray &id, const char *info, va_list args) { if (!m_enabled) diff --git a/src/runtime/q3dsprofiler_p.h b/src/runtime/q3dsprofiler_p.h index 7a65f45..2bc391c 100644 --- a/src/runtime/q3dsprofiler_p.h +++ b/src/runtime/q3dsprofiler_p.h @@ -89,6 +89,7 @@ public: ShaderProgramObject }; void trackNewObject(QObject *obj, ObjectType type, const QByteArray &id, const char *info, ...); + void trackNewObject(QObject *obj, ObjectType type, const QByteArray &id, const QString &info); void vtrackNewObject(QObject *obj, ObjectType type, const QByteArray &id, const char *info, va_list args); void updateObjectInfo(QObject *obj, ObjectType type, const QByteArray &id, const char *info, ...); diff --git a/src/runtime/q3dsscenemanager.cpp b/src/runtime/q3dsscenemanager.cpp index 060b43c..391573b 100644 --- a/src/runtime/q3dsscenemanager.cpp +++ b/src/runtime/q3dsscenemanager.cpp @@ -47,6 +47,7 @@ #include "q3dslogging_p.h" #include "q3dsviewportsettings_p.h" #include "q3dstextmesh_p.h" +#include "q3dsimagemanager_p.h" #if QT_CONFIG(q3ds_profileui) #include "profileui/q3dsprofileui_p.h" #include "q3dsconsolecommands_p.h" @@ -859,14 +860,19 @@ Q3DSSceneManager::Scene Q3DSSceneManager::buildScene(Q3DSUipPresentation *presen // Do some pre-processing and early setup for some objects. initSubTree(m_scene); + m_engine->loadSlideResources(m_masterSlide, m_presentation); + m_engine->loadSlideResources(m_currentSlide, m_presentation); + // Build the (offscreen) Qt3D scene m_layerContainerFg = new Qt3DRender::QFrameGraphNode(frameGraphRoot); new Qt3DRender::QNoDraw(m_layerContainerFg); // in case there are no layers at all + Q3DSUipPresentation::forAllLayers(m_scene, [=](Q3DSLayerNode *layer3DS) { if (layer3DS->sourcePath().isEmpty()) buildLayer(layer3DS, m_layerContainerFg, m_outputPixelSize); else buildSubPresentationLayer(layer3DS, m_outputPixelSize); + }); // The Scene object may have non-layer children. @@ -879,7 +885,6 @@ Q3DSSceneManager::Scene Q3DSSceneManager::buildScene(Q3DSUipPresentation *presen // Onscreen (or not) compositor (still offscreen when this is a subpresentation) buildCompositor(frameGraphRoot, m_rootEntity); - // Profiling UI (main presentation only) #if QT_CONFIG(q3ds_profileui) if (!m_flags.testFlag(SubPresentation)) { @@ -2181,7 +2186,7 @@ void Q3DSSceneManager::setLayerProperties(Q3DSLayerNode *layer3DS) if (layer3DS->lightProbe()) { // initialize light probe parameters if necessary if (!data->iblProbeData.lightProbeTexture) { - data->iblProbeData.lightProbeTexture = Q3DSImageManager::instance().newTextureForImage( + data->iblProbeData.lightProbeTexture = Q3DSImageManager::instance().newReloadableTextureForImage( m_rootEntity, Q3DSImageManager::GenerateMipMapsForIBL, layer3DS->lightProbe()->id(), m_profiler, "iblProbe texture for image %s", layer3DS->lightProbe()->id().constData()); @@ -2204,59 +2209,75 @@ void Q3DSSceneManager::setLayerProperties(Q3DSLayerNode *layer3DS) // Update light probe parameter values // also sets min/mag and generates mipmaps - Q3DSImageManager::instance().setSource(data->iblProbeData.lightProbeTexture, - QUrl::fromLocalFile(layer3DS->lightProbe()->sourcePath()), - m_presentation->preferKtx()); - data->iblProbeData.lightProbeSampler->setValue(QVariant::fromValue(data->iblProbeData.lightProbeTexture)); + data->iblProbeData.lightProbeTexture->setSource(m_presentation->imageUrl( + layer3DS->lightProbe()->sourcePath())); + data->iblProbeData.lightProbeTexture->onLoad([](Qt3DRender::QAbstractTexture *texture, + Q3DSLayerNode *layer3DS) { + Q3DSLayerAttached *data = static_cast<Q3DSLayerAttached *>(layer3DS->attached()); + data->iblProbeData.lightProbeSampler->setValue(QVariant::fromValue(texture)); - Qt3DRender::QTextureWrapMode wrapMode; + Qt3DRender::QTextureWrapMode wrapMode; - switch (layer3DS->lightProbe()->horizontalTiling()) { - case Q3DSImage::Tiled: - wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); - break; - case Q3DSImage::Mirrored: - wrapMode.setX(Qt3DRender::QTextureWrapMode::MirroredRepeat); - break; - default: - wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); - break; - } + switch (layer3DS->lightProbe()->horizontalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setX(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } - switch (layer3DS->lightProbe()->verticalTiling()) { - case Q3DSImage::Tiled: - wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); - break; - case Q3DSImage::Mirrored: - wrapMode.setY(Qt3DRender::QTextureWrapMode::MirroredRepeat); - break; - default: - wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); - break; - } + switch (layer3DS->lightProbe()->verticalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setY(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } - Qt3DRender::QAbstractTexture *texture = data->iblProbeData.lightProbeTexture; - texture->setWrapMode(wrapMode); + texture->setWrapMode(wrapMode); - const QMatrix4x4 &textureTransform = layer3DS->lightProbe()->textureTransform(); - const float *m = textureTransform.constData(); + const QMatrix4x4 &textureTransform = layer3DS->lightProbe()->textureTransform(); + const float *m = textureTransform.constData(); - // offsets.w = max mip level - const QSize texSize = Q3DSImageManager::instance().size(texture); - float mipLevels = float(qCeil(qLog2(qMax(texSize.width(), texSize.height())))); - QVector4D offsets(m[12], m[13], 0.0f, mipLevels); - data->iblProbeData.lightProbeOffset->setValue(offsets); + const QSize texSize = Q3DSImageManager::instance().size(texture); + float mipLevels = float(qCeil(qLog2(qMax(texSize.width(), texSize.height())))); + QVector4D offsets(m[12], m[13], 0.0f, mipLevels); + data->iblProbeData.lightProbeOffset->setValue(offsets); + + QVector4D rotations(m[0], m[4], m[1], m[5]); + data->iblProbeData.lightProbeRotation->setValue(rotations); + }, layer3DS); + + data->iblProbeData.lightProbeTexture->onUnload([](Qt3DRender::QAbstractTexture *texture, + Q3DSLayerNode *layer3DS) { + Q_UNUSED(texture); + Q3DSLayerAttached *data = static_cast<Q3DSLayerAttached *>(layer3DS->attached()); + data->iblProbeData.lightProbeSampler->setValue({}); + QVector4D offsets(0.f, 0.f, 0.f, 0.f); + data->iblProbeData.lightProbeOffset->setValue(offsets); + + QVector4D rotations(0.f, 0.f, 0.f, 0.f); + data->iblProbeData.lightProbeRotation->setValue(rotations); + }, layer3DS); - QVector4D rotations(m[0], m[4], m[1], m[5]); - data->iblProbeData.lightProbeRotation->setValue(rotations); if (layer3DS->lightProbe2()) { // Initialize light probe 2 parameters if (!data->iblProbeData.lightProbe2Texture) { - data->iblProbeData.lightProbe2Texture = Q3DSImageManager::instance().newTextureForImage( + data->iblProbeData.lightProbe2Texture + = Q3DSImageManager::instance().newReloadableTextureForImage( m_rootEntity, Q3DSImageManager::GenerateMipMapsForIBL, layer3DS->lightProbe2()->id(), - m_profiler, "iblProbe2 texture for image %s", layer3DS->lightProbe2()->id().constData()); + m_profiler, "iblProbe2 texture for image %s", + layer3DS->lightProbe2()->id().constData()); } if (!data->iblProbeData.lightProbe2Sampler) { @@ -2267,22 +2288,63 @@ void Q3DSSceneManager::setLayerProperties(Q3DSLayerNode *layer3DS) // Update light probe 2 parameter values // also sets min/mag and generates mipmaps - Q3DSImageManager::instance().setSource(data->iblProbeData.lightProbe2Texture, - QUrl::fromLocalFile(layer3DS->lightProbe2()->sourcePath()), - m_presentation->preferKtx()); - data->iblProbeData.lightProbe2Sampler->setValue(QVariant::fromValue(data->iblProbeData.lightProbe2Texture)); + data->iblProbeData.lightProbe2Texture->setSource( + m_presentation->imageUrl(layer3DS->lightProbe2()->sourcePath())); + data->iblProbeData.lightProbe2Texture->onLoad([](Qt3DRender::QAbstractTexture *texture, + Q3DSLayerNode *layer3DS) { + Q3DSLayerAttached *data = static_cast<Q3DSLayerAttached *>(layer3DS->attached()); + data->iblProbeData.lightProbe2Sampler->setValue(QVariant::fromValue(texture)); + + Qt3DRender::QTextureWrapMode wrapMode; + + switch (layer3DS->lightProbe()->horizontalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setX(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } - data->iblProbeData.lightProbe2Texture->setWrapMode(wrapMode); + switch (layer3DS->lightProbe()->verticalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setY(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } - QVector4D probe2Props(layer3DS->probe2Window(), layer3DS->probe2Pos(), layer3DS->probe2Fade(), 1.0f); - data->iblProbeData.lightProbe2Properties->setValue(probe2Props); - const QMatrix4x4 &textureTransform = layer3DS->lightProbe2()->textureTransform(); - const float *m = textureTransform.constData(); - QVector4D probeProps(m[12], m[13], layer3DS->probeHorizon(), layer3DS->probeBright() * 0.01f); - data->iblProbeData.lightProbeProperties->setValue(probeProps); + texture->setWrapMode(wrapMode); + + QVector4D probe2Props(layer3DS->probe2Window(), layer3DS->probe2Pos(), + layer3DS->probe2Fade(), 1.0f); + data->iblProbeData.lightProbe2Properties->setValue(probe2Props); + const QMatrix4x4 &textureTransform = layer3DS->lightProbe2()->textureTransform(); + const float *m = textureTransform.constData(); + QVector4D probeProps(m[12], m[13], layer3DS->probeHorizon(), + layer3DS->probeBright() * 0.01f); + data->iblProbeData.lightProbeProperties->setValue(probeProps); + }, layer3DS); + data->iblProbeData.lightProbe2Texture->onUnload( + [](Qt3DRender::QAbstractTexture *texture, Q3DSLayerNode *layer3DS) { + Q_UNUSED(texture); + Q3DSLayerAttached *data = static_cast<Q3DSLayerAttached *>(layer3DS->attached()); + data->iblProbeData.lightProbe2Sampler->setValue({}); + data->iblProbeData.lightProbe2Properties->setValue({}); + data->iblProbeData.lightProbeProperties->setValue({}); + }, layer3DS); } else { data->iblProbeData.lightProbe2Properties->setValue(QVector4D(0.0f, 0.0f, 0.0f, 0.0f)); - data->iblProbeData.lightProbeProperties->setValue(QVector4D(0.0f, 0.0f, layer3DS->probeHorizon(), layer3DS->probeBright() * 0.01f)); + data->iblProbeData.lightProbeProperties->setValue( + QVector4D(0.0f, 0.0f, layer3DS->probeHorizon(), + layer3DS->probeBright() * 0.01f)); } } } @@ -5786,7 +5848,6 @@ static void addShadowSsaoParams(Q3DSLayerAttached *layerData, QVector<Qt3DRender void Q3DSSceneManager::buildModelMaterial(Q3DSModelNode *model3DS) { // Scene building phase 2: all lights are known -> generate actual Qt3D materials - Q3DSModelAttached *modelData = static_cast<Q3DSModelAttached *>(model3DS->attached()); if (!modelData) return; @@ -5814,7 +5875,6 @@ void Q3DSSceneManager::buildModelMaterial(Q3DSModelNode *model3DS) // matters so much. lightNodes.resize(m_gfxLimits.maxLightsPerLayer); } - for (Q3DSModelAttached::SubMesh &sm : modelData->subMeshes) { if (sm.resolvedMaterial && !sm.materialComponent && sm.resolvedMaterial->type() != Q3DSGraphObject::ReferencedMaterial) { Q_ASSERT(sm.resolvedMaterial->attached()); @@ -6092,15 +6152,58 @@ void Q3DSSceneManager::prepareTextureParameters(Q3DSTextureParameters &texturePa textureParameters.size = new Qt3DRender::QParameter; textureParameters.size->setName(name + QLatin1String("_size")); - - textureParameters.texture = Q3DSImageManager::instance().newTextureForImage( - m_rootEntity, 0, image3DS->id(), m_profiler, "Texture for image %s", image3DS->id().constData()); } void Q3DSSceneManager::updateTextureParameters(Q3DSTextureParameters &textureParameters, Q3DSImage *image) { // note that this function is called frequently (whenever any Image parameter changes) + auto setupTexture = [](Q3DSTextureParameters &textureParameters, Q3DSImage *image) { + Qt3DRender::QTextureWrapMode wrapMode; + switch (image->horizontalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setX(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } + switch (image->verticalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setY(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } + + Qt3DRender::QAbstractTexture *texture + = textureParameters.sampler->value().value<Qt3DRender::QAbstractTexture *>(); + Q_ASSERT(texture || !image->subPresentation().isEmpty()); + if (!texture) + return; + + texture->setWrapMode(wrapMode); + // min/mag are already set at this point + + const QMatrix4x4 &textureTransform = image->textureTransform(); + const float *m = textureTransform.constData(); + + QVector3D offsets(m[12], m[13], image->hasPremultipliedAlpha() ? 1 : 0); + textureParameters.offsets->setValue(offsets); + + QVector4D rotations(m[0], m[4], m[1], m[5]); + textureParameters.rotations->setValue(rotations); + const QSize size = Q3DSImageManager::instance().size(texture); + textureParameters.size->setValue(QVector2D(size.width(), size.height())); + }; + if (!image->subPresentation().isEmpty()) { if (textureParameters.subPresId != image->subPresentation()) { textureParameters.subPresId = image->subPresentation(); @@ -6112,60 +6215,40 @@ void Q3DSSceneManager::updateTextureParameters(Q3DSTextureParameters &texturePar setImageTextureFromSubPresentation(textureParameters.sampler, image); } } + setupTexture(textureParameters, image); } else if (!image->sourcePath().isEmpty()) { - Q3DSImageManager::instance().setSource(textureParameters.texture, QUrl::fromLocalFile(image->sourcePath()), - m_presentation->preferKtx()); - textureParameters.sampler->setValue(QVariant::fromValue(textureParameters.texture)); + if (!textureParameters.textureSource) { + textureParameters.textureSource + = Q3DSImageManager::instance().newReloadableTextureForImage( + m_rootEntity, 0, image->id(), m_profiler, "Texture for image %s", + image->id().constData()); + } + textureParameters.textureSource->setSource(m_presentation->imageUrl(image->sourcePath())); + textureParameters.textureSource->onLoad( + [setupTexture](Qt3DRender::QAbstractTexture *texture, + Q3DSTextureParameters *param, Q3DSImage *image) { + param->sampler->setValue(QVariant::fromValue(texture)); + setupTexture(*param, image); + }, &textureParameters, image); + textureParameters.textureSource->onUnload([](Qt3DRender::QAbstractTexture *texture, + Q3DSTextureParameters *param) { + Q_UNUSED(texture); + param->sampler->setValue({}); + param->offsets->setValue({}); + param->rotations->setValue({}); + param->size->setValue({}); + }, &textureParameters); } else if (!image->customImage().isNull()) { + textureParameters.texture + = Q3DSImageManager::instance().newTextureForImage(m_rootEntity, 0, image->id(), + m_profiler, QStringLiteral("Texture for image %1").arg(image->id().constData())); Q3DSImageManager::instance().setSource(textureParameters.texture, image->customImage()); textureParameters.sampler->setValue(QVariant::fromValue(textureParameters.texture)); + setupTexture(textureParameters, image); } else { textureParameters.sampler->setValue(QVariant::fromValue(dummyTexture(image->id()))); + setupTexture(textureParameters, image); } - - Qt3DRender::QTextureWrapMode wrapMode; - switch (image->horizontalTiling()) { - case Q3DSImage::Tiled: - wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); - break; - case Q3DSImage::Mirrored: - wrapMode.setX(Qt3DRender::QTextureWrapMode::MirroredRepeat); - break; - default: - wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); - break; - } - switch (image->verticalTiling()) { - case Q3DSImage::Tiled: - wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); - break; - case Q3DSImage::Mirrored: - wrapMode.setY(Qt3DRender::QTextureWrapMode::MirroredRepeat); - break; - default: - wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); - break; - } - - Qt3DRender::QAbstractTexture *texture = textureParameters.sampler->value().value<Qt3DRender::QAbstractTexture *>(); - Q_ASSERT(texture || !image->subPresentation().isEmpty()); - if (!texture) - return; - - texture->setWrapMode(wrapMode); - // min/mag are already set at this point - - const QMatrix4x4 &textureTransform = image->textureTransform(); - const float *m = textureTransform.constData(); - - QVector3D offsets(m[12], m[13], image->hasPremultipliedAlpha() ? 1 : 0); - textureParameters.offsets->setValue(offsets); - - QVector4D rotations(m[0], m[4], m[1], m[5]); - textureParameters.rotations->setValue(rotations); - - const QSize size = Q3DSImageManager::instance().size(texture); - textureParameters.size->setValue(QVector2D(size.width(), size.height())); } void Q3DSSceneManager::setImageTextureFromSubPresentation(Qt3DRender::QParameter *sampler, Q3DSImage *image) @@ -6438,10 +6521,11 @@ QVector<Qt3DRender::QParameter *> Q3DSSceneManager::prepareDefaultMaterial(Q3DSD iblOverrideImage = m->lightProbe(); if (iblOverrideImage) { if (!data->lightProbeOverrideTexture) { - data->lightProbeOverrideTexture = Q3DSImageManager::instance().newTextureForImage( + data->lightProbeOverrideTexture + = Q3DSImageManager::instance().newReloadableTextureForImage( m_rootEntity, Q3DSImageManager::GenerateMipMapsForIBL, - iblOverrideImage->id(), - m_profiler, "Texture for image %s", iblOverrideImage->id().constData()); + iblOverrideImage->id(), m_profiler, + "Texture for image %s", iblOverrideImage->id().constData()); data->lightProbeSampler = new Qt3DRender::QParameter; data->lightProbeSampler->setName(QLatin1String("light_probe")); @@ -6583,6 +6667,48 @@ void Q3DSSceneManager::updateDefaultMaterial(Q3DSDefaultMaterial *m, Q3DSReferen // IBL if (iblOverride) { + auto setupTexture = [](Qt3DRender::QAbstractTexture *texture, + Q3DSDefaultMaterialAttached *data, Q3DSImage *iblOverride) { + Qt3DRender::QTextureWrapMode wrapMode; + + switch (iblOverride->horizontalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setX(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } + + switch (iblOverride->verticalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setY(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } + + texture->setWrapMode(wrapMode); + + const QMatrix4x4 &textureTransform = iblOverride->textureTransform(); + const float *m = textureTransform.constData(); + + // offsets.w = max mip level + const QSize texSize = Q3DSImageManager::instance().size(texture); + float mipLevels = float(qCeil(qLog2(qMax(texSize.width(), texSize.height())))); + QVector4D offsets(m[12], m[13], 0.0f, mipLevels); + data->lightProbeOffset->setValue(offsets); + + QVector4D rotations(m[0], m[4], m[1], m[5]); + data->lightProbeRotation->setValue(rotations); + }; // also sets min/mag and generates mipmaps if (!iblOverride->subPresentation().isEmpty()) { if (m_subPresentations.isEmpty()) @@ -6590,52 +6716,23 @@ void Q3DSSceneManager::updateDefaultMaterial(Q3DSDefaultMaterial *m, Q3DSReferen else setImageTextureFromSubPresentation(data->lightProbeSampler, iblOverride); } else { - Q3DSImageManager::instance().setSource(data->lightProbeOverrideTexture, - QUrl::fromLocalFile(iblOverride->sourcePath()), - m_presentation->preferKtx()); - data->lightProbeSampler->setValue(QVariant::fromValue(data->lightProbeOverrideTexture)); - } - - Qt3DRender::QTextureWrapMode wrapMode; - - switch (iblOverride->horizontalTiling()) { - case Q3DSImage::Tiled: - wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); - break; - case Q3DSImage::Mirrored: - wrapMode.setX(Qt3DRender::QTextureWrapMode::MirroredRepeat); - break; - default: - wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); - break; - } - - switch (iblOverride->verticalTiling()) { - case Q3DSImage::Tiled: - wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); - break; - case Q3DSImage::Mirrored: - wrapMode.setY(Qt3DRender::QTextureWrapMode::MirroredRepeat); - break; - default: - wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); - break; + data->lightProbeOverrideTexture->setSource( + m_presentation->imageUrl(iblOverride->sourcePath())); + data->lightProbeOverrideTexture->onLoad([setupTexture]( + Qt3DRender::QAbstractTexture *texture, + Q3DSDefaultMaterialAttached *data, + Q3DSImage *iblOverride) { + data->lightProbeSampler->setValue(QVariant::fromValue(texture)); + setupTexture(texture, data, iblOverride); + }, data, iblOverride); + data->lightProbeOverrideTexture->onUnload([](Qt3DRender::QAbstractTexture *texture, + Q3DSDefaultMaterialAttached *data) { + Q_UNUSED(texture); + data->lightProbeSampler->setValue({}); + data->lightProbeOffset->setValue({}); + data->lightProbeRotation->setValue({}); + }, data); } - - Q_ASSERT(data->lightProbeOverrideTexture); - data->lightProbeOverrideTexture->setWrapMode(wrapMode); - - const QMatrix4x4 &textureTransform = iblOverride->textureTransform(); - const float *m = textureTransform.constData(); - - // offsets.w = max mip level - const QSize texSize = Q3DSImageManager::instance().size(data->lightProbeOverrideTexture); - float mipLevels = float(qCeil(qLog2(qMax(texSize.width(), texSize.height())))); - QVector4D offsets(m[12], m[13], 0.0f, mipLevels); - data->lightProbeOffset->setValue(offsets); - - QVector4D rotations(m[0], m[4], m[1], m[5]); - data->lightProbeRotation->setValue(rotations); } } @@ -6663,71 +6760,91 @@ static inline void forAllCustomProperties(Q3DSEffectInstance *eff3DS, CustomProp iterateCustomProperties(eff3DS->dynamicProperties(), eff3DS->effect()->properties(), callback); } -Qt3DRender::QAbstractTexture *Q3DSSceneManager::createCustomPropertyTexture(const Q3DSCustomPropertyParameter &p, const QByteArray &id) +void Q3DSSceneManager::createCustomPropertyTexture(Q3DSCustomPropertyParameter &p, + const QByteArray &id) { + // now override the defaults set in setSource() with whatever the metadata specifies + auto setupTexture = [](Q3DSCustomPropertyParameter *p, Qt3DRender::QAbstractTexture *texture) { + switch (p->meta.magFilterType) { + case Q3DSMaterial::Nearest: + texture->setMagnificationFilter(Qt3DRender::QAbstractTexture::Nearest); + break; + default: + texture->setMagnificationFilter(Qt3DRender::QAbstractTexture::Linear); + break; + } + + switch (p->meta.minFilterType) { + case Q3DSMaterial::Nearest: + texture->setMinificationFilter(Qt3DRender::QAbstractTexture::Nearest); + break; + case Q3DSMaterial::Linear: + texture->setMinificationFilter(Qt3DRender::QAbstractTexture::Linear); + break; + case Q3DSMaterial::NearestMipmapNearest: + texture->setMinificationFilter(Qt3DRender::QAbstractTexture::NearestMipMapNearest); + texture->setGenerateMipMaps(true); + break; + case Q3DSMaterial::NearestMipmapLinear: + texture->setMinificationFilter(Qt3DRender::QAbstractTexture::NearestMipMapLinear); + texture->setGenerateMipMaps(true); + break; + case Q3DSMaterial::LinearMipmapNearest: + texture->setMinificationFilter(Qt3DRender::QAbstractTexture::LinearMipMapNearest); + texture->setGenerateMipMaps(true); + break; + default: + texture->setMinificationFilter(Qt3DRender::QAbstractTexture::LinearMipMapLinear); + texture->setGenerateMipMaps(true); + break; + } + + Qt3DRender::QTextureWrapMode wrapMode; + switch (p->meta.clampType) { + case Q3DSMaterial::Repeat: + wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); + wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); + break; + default: + wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); + wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } + texture->setWrapMode(wrapMode); + }; + const QString source = p.inputValue.toString(); Qt3DRender::QAbstractTexture *texture; if (source.isEmpty()) { texture = dummyTexture(id); + setupTexture(&p, texture); + p.param->setValue(QVariant::fromValue(texture)); } else { - texture = Q3DSImageManager::instance().newTextureForImage(m_rootEntity, 0, id, m_profiler, - "Custom property texture %s", qPrintable(source)); + p.texture = Q3DSImageManager::instance().newReloadableTextureForImage( + m_rootEntity, 0, id, m_profiler, "Custom property texture %s", + qPrintable(source)); qCDebug(lcScene, "Creating custom property texture %s", qPrintable(source)); - Q3DSImageManager::instance().setSource(texture, QUrl::fromLocalFile(source), - m_presentation->preferKtx()); - } - - // now override the defaults set in setSource() with whatever the metadata specifies - - switch (p.meta.magFilterType) { - case Q3DSMaterial::Nearest: - texture->setMagnificationFilter(Qt3DRender::QAbstractTexture::Nearest); - break; - default: - texture->setMagnificationFilter(Qt3DRender::QAbstractTexture::Linear); - break; - } - - switch (p.meta.minFilterType) { - case Q3DSMaterial::Nearest: - texture->setMinificationFilter(Qt3DRender::QAbstractTexture::Nearest); - break; - case Q3DSMaterial::Linear: - texture->setMinificationFilter(Qt3DRender::QAbstractTexture::Linear); - break; - case Q3DSMaterial::NearestMipmapNearest: - texture->setMinificationFilter(Qt3DRender::QAbstractTexture::NearestMipMapNearest); - texture->setGenerateMipMaps(true); - break; - case Q3DSMaterial::NearestMipmapLinear: - texture->setMinificationFilter(Qt3DRender::QAbstractTexture::NearestMipMapLinear); - texture->setGenerateMipMaps(true); - break; - case Q3DSMaterial::LinearMipmapNearest: - texture->setMinificationFilter(Qt3DRender::QAbstractTexture::LinearMipMapNearest); - texture->setGenerateMipMaps(true); - break; - default: - texture->setMinificationFilter(Qt3DRender::QAbstractTexture::LinearMipMapLinear); - texture->setGenerateMipMaps(true); - break; - - } - - Qt3DRender::QTextureWrapMode wrapMode; - switch (p.meta.clampType) { - case Q3DSMaterial::Repeat: - wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); - wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); - break; - default: - wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); - wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); - break; + p.texture->setSource(m_presentation->imageUrl(source)); + p.texture->onLoad([setupTexture](Qt3DRender::QAbstractTexture *texture, + Q3DSCustomPropertyParameter *p) { + p->param->setValue(QVariant::fromValue(texture)); + if (p->texFlagParam) + p->texFlagParam->setValue(1); + const QSize size = Q3DSImageManager::instance().size(texture); + const bool isPremultiplied = false; + if (p->texInfoParam) { + p->texInfoParam->setValue(QVector4D(size.width(), size.height(), + isPremultiplied ? 1 : 0, 0)); + } + setupTexture(p, texture); + }, &p); + p.texture->onUnload([](Qt3DRender::QAbstractTexture *texture, + Q3DSCustomPropertyParameter *p) { + Q_UNUSED(texture); + p->param->setValue({}); + p->texInfoParam->setValue({}); + }, &p); } - texture->setWrapMode(wrapMode); - - return texture; } QVector<Qt3DRender::QParameter *> Q3DSSceneManager::prepareCustomMaterial(Q3DSCustomMaterialInstance *m, Q3DSReferencedMaterial *rm, Q3DSModelNode *model3DS) @@ -6819,7 +6936,8 @@ QVector<Qt3DRender::QParameter *> Q3DSSceneManager::prepareCustomMaterial(Q3DSCu iblOverrideImage = m->lightProbe(); if (iblOverrideImage) { if (!data->lightProbeOverrideTexture) { - data->lightProbeOverrideTexture = Q3DSImageManager::instance().newTextureForImage( + data->lightProbeOverrideTexture + = Q3DSImageManager::instance().newReloadableTextureForImage( m_rootEntity, Q3DSImageManager::GenerateMipMapsForIBL, iblOverrideImage->id(), m_profiler, "Texture for image %s", iblOverrideImage->id().constData()); @@ -6867,7 +6985,7 @@ void Q3DSSceneManager::updateCustomMaterial(Q3DSCustomMaterialInstance *m, Q3DSR // point whereas we need a proper Qt 3D texture. switch (p.meta.type) { case Q3DS::Texture: - p.param->setValue(QVariant::fromValue(createCustomPropertyTexture(p, m->id()))); + createCustomPropertyTexture(p, m->id()); break; // Buffer, Image2D, etc. are not used for custom materials @@ -6917,52 +7035,61 @@ void Q3DSSceneManager::updateCustomMaterial(Q3DSCustomMaterialInstance *m, Q3DSR // IBL if (iblOverride) { // also sets min/mag and generates mipmaps - Q3DSImageManager::instance().setSource(data->lightProbeOverrideTexture, - QUrl::fromLocalFile(iblOverride->sourcePath()), - m_presentation->preferKtx()); - data->lightProbeSampler->setValue(QVariant::fromValue(data->lightProbeOverrideTexture)); - - Qt3DRender::QTextureWrapMode wrapMode; - wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); - - switch (iblOverride->horizontalTiling()) { - case Q3DSImage::Tiled: + data->lightProbeOverrideTexture->setSource( + m_presentation->imageUrl(iblOverride->sourcePath())); + data->lightProbeOverrideTexture->onLoad([](Qt3DRender::QAbstractTexture *texture, + Q3DSImage *iblOverride, + Q3DSCustomMaterialAttached *data) { + data->lightProbeSampler->setValue(QVariant::fromValue(texture)); + + Qt3DRender::QTextureWrapMode wrapMode; wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); - break; - case Q3DSImage::Mirrored: - wrapMode.setX(Qt3DRender::QTextureWrapMode::MirroredRepeat); - break; - default: - wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); - break; - } - switch (iblOverride->verticalTiling()) { - case Q3DSImage::Tiled: - wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); - break; - case Q3DSImage::Mirrored: - wrapMode.setY(Qt3DRender::QTextureWrapMode::MirroredRepeat); - break; - default: - wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); - break; - } + switch (iblOverride->horizontalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setX(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setX(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setX(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } + + switch (iblOverride->verticalTiling()) { + case Q3DSImage::Tiled: + wrapMode.setY(Qt3DRender::QTextureWrapMode::Repeat); + break; + case Q3DSImage::Mirrored: + wrapMode.setY(Qt3DRender::QTextureWrapMode::MirroredRepeat); + break; + default: + wrapMode.setY(Qt3DRender::QTextureWrapMode::ClampToEdge); + break; + } - Q_ASSERT(data->lightProbeOverrideTexture); - data->lightProbeOverrideTexture->setWrapMode(wrapMode); + texture->setWrapMode(wrapMode); - const QMatrix4x4 &textureTransform = iblOverride->textureTransform(); - const float *m = textureTransform.constData(); + const QMatrix4x4 &textureTransform = iblOverride->textureTransform(); + const float *m = textureTransform.constData(); - // offsets.w = max mip level - const QSize texSize = Q3DSImageManager::instance().size(data->lightProbeOverrideTexture); - float mipLevels = float(qCeil(qLog2(qMax(texSize.width(), texSize.height())))); - QVector4D offsets(m[12], m[13], 0.0f, mipLevels); - data->lightProbeOffset->setValue(offsets); + // offsets.w = max mip level + const QSize texSize = Q3DSImageManager::instance().size(texture); + float mipLevels = float(qCeil(qLog2(qMax(texSize.width(), texSize.height())))); + QVector4D offsets(m[12], m[13], 0.0f, mipLevels); + data->lightProbeOffset->setValue(offsets); - QVector4D rotations(m[0], m[4], m[1], m[5]); - data->lightProbeRotation->setValue(rotations); + QVector4D rotations(m[0], m[4], m[1], m[5]); + data->lightProbeRotation->setValue(rotations); + }, iblOverride, data); + data->lightProbeOverrideTexture->onUnload([](Qt3DRender::QAbstractTexture *texture, + Q3DSCustomMaterialAttached *data) { + Q_UNUSED(texture); + data->lightProbeSampler->setValue({}); + data->lightProbeOffset->setValue({}); + data->lightProbeRotation->setValue({}); + }, data); } } @@ -7945,10 +8072,7 @@ void Q3DSSceneManager::updateEffect(Q3DSEffectInstance *eff3DS) switch (p.meta.type) { case Q3DS::Texture: { - Qt3DRender::QAbstractTexture *tex = createCustomPropertyTexture(p, eff3DS->id()); - p.param->setValue(QVariant::fromValue(tex)); - setTextureInfoUniform(p.texInfoParam, tex); - p.texFlagParam->setValue(1); + createCustomPropertyTexture(p, eff3DS->id()); } break; diff --git a/src/runtime/q3dsscenemanager_p.h b/src/runtime/q3dsscenemanager_p.h index 4a0b2c0..f8c836d 100644 --- a/src/runtime/q3dsscenemanager_p.h +++ b/src/runtime/q3dsscenemanager_p.h @@ -115,6 +115,9 @@ namespace Qt3DExtras { class QPlaneMesh; } +class ReloadableTexture; +typedef QSharedPointer<ReloadableTexture> ReloadableTexturePtr; + struct Q3DSEyeData { Qt3DRender::QCameraSelector *cameraSelector = nullptr; @@ -405,8 +408,8 @@ public: } advBlend; struct IBLProbeData { - Qt3DRender::QAbstractTexture *lightProbeTexture = nullptr; - Qt3DRender::QAbstractTexture *lightProbe2Texture = nullptr; + ReloadableTexturePtr lightProbeTexture = nullptr; + ReloadableTexturePtr lightProbe2Texture = nullptr; Qt3DRender::QParameter *lightProbeSampler = nullptr; Qt3DRender::QParameter *lightProbeRotation = nullptr; @@ -511,6 +514,7 @@ struct Q3DSTextureParameters QVector<Qt3DRender::QParameter *> parameters() const { return { sampler, offsets, rotations, size }; } Qt3DRender::QAbstractTexture *texture = nullptr; + ReloadableTexturePtr textureSource = nullptr; QString subPresId; }; @@ -565,7 +569,7 @@ public: Q3DSTextureParameters lightmapShadowParams; // IBL QMetaObject::Connection updateOffsetConnection; - Qt3DRender::QAbstractTexture *lightProbeOverrideTexture = nullptr; + ReloadableTexturePtr lightProbeOverrideTexture = nullptr; Qt3DRender::QParameter *lightProbeSampler = nullptr; Qt3DRender::QParameter *lightProbeRotation = nullptr; Qt3DRender::QParameter *lightProbeOffset = nullptr; @@ -578,6 +582,7 @@ struct Q3DSCustomPropertyParameter { meta(meta_) { } Q3DSCustomPropertyParameter() { } + ReloadableTexturePtr texture = nullptr; Qt3DRender::QParameter *param = nullptr; QVariant inputValue; // e.g. Texture: inputValue is a string whereas param->value is a QAbstractTexture* Q3DSMaterial::PropertyElement meta; @@ -600,7 +605,7 @@ public: // IBL QMetaObject::Connection updateOffsetConnection; - Qt3DRender::QAbstractTexture *lightProbeOverrideTexture = nullptr; + ReloadableTexturePtr lightProbeOverrideTexture = nullptr; Qt3DRender::QParameter *lightProbeSampler = nullptr; Qt3DRender::QParameter *lightProbeRotation = nullptr; Qt3DRender::QParameter *lightProbeOffset = nullptr; @@ -807,6 +812,7 @@ public: Q3DSInputManager *inputManager() { return m_inputManager; } Q3DSTextRenderer *textRenderer() const { return m_textRenderer; } Q3DSEngine *engine() const { return m_engine; } + Q3DSUipPresentation *presentation() const { return m_presentation; } void setEyeDepthTextureEnabled(Q3DSLayerNode *layer3DS, Q3DSEyeData *eyeData, bool enabled); void setDepthTextureEnabled(Q3DSLayerNode *layer3DS, bool enabled); @@ -899,7 +905,7 @@ private: bool checkImageTransparency(Q3DSImage *image) const; void prepareTextureParameters(Q3DSTextureParameters &textureParameters, const QString &name, Q3DSImage *image3DS); QVector<Qt3DRender::QParameter *> prepareDefaultMaterial(Q3DSDefaultMaterial *m, Q3DSReferencedMaterial *rm, Q3DSModelNode *model3DS); - Qt3DRender::QAbstractTexture *createCustomPropertyTexture(const Q3DSCustomPropertyParameter &p, const QByteArray &id); + void createCustomPropertyTexture(Q3DSCustomPropertyParameter &p, const QByteArray &id); QVector<Qt3DRender::QParameter *> prepareCustomMaterial(Q3DSCustomMaterialInstance *m, Q3DSReferencedMaterial *rm, Q3DSModelNode *model3DS); void setImageTextureFromSubPresentation(Qt3DRender::QParameter *sampler, Q3DSImage *image); void updateTextureParameters(Q3DSTextureParameters &textureParameters, Q3DSImage *image); diff --git a/src/runtime/q3dsslideplayer.cpp b/src/runtime/q3dsslideplayer.cpp index 0bf850f..a2a001c 100644 --- a/src/runtime/q3dsslideplayer.cpp +++ b/src/runtime/q3dsslideplayer.cpp @@ -34,6 +34,7 @@ #include <QtCore/qmetaobject.h> #include "q3dsscenemanager_p.h" #include "q3dsanimationmanager_p.h" +#include "q3dsengine_p.h" #include "q3dslogging_p.h" #include <Qt3DAnimation/qclipanimator.h> @@ -518,6 +519,19 @@ void Q3DSSlidePlayer::setSlideDeck(Q3DSSlideDeck *slideDeck) forAllSlides(slideDeck); + m_sceneManager->engine()->loadSlideResources(slideDeck->masterSlide(), + m_sceneManager->presentation()); + for (auto object : slideDeck->masterSlide()->objects()) { + if (object->type() == Q3DSGraphObject::Component + && object->state() == Q3DSGraphObject::Enabled) { + Q3DSComponentNode *comp = static_cast<Q3DSComponentNode *>(object); + m_sceneManager->engine()->loadSlideResources(comp->masterSlide(), + m_sceneManager->presentation()); + m_sceneManager->engine()->loadSlideResources(comp->currentSlide(), + m_sceneManager->presentation()); + + } + } setInternalState(PlayerState::Ready); Q_EMIT slideDeckChanged(m_data.slideDeck); } @@ -597,6 +611,17 @@ void Q3DSSlidePlayer::setInternalState(Q3DSSlidePlayer::PlayerState state) { m_data.pendingState = state; Q3DSSlide *currentSlide = m_data.slideDeck->currentSlide(); + m_sceneManager->engine()->loadSlideResources(currentSlide, m_sceneManager->presentation()); + for (auto object : currentSlide->objects()) { + if (object->type() == Q3DSGraphObject::Component + && object->state() == Q3DSGraphObject::Enabled) { + Q3DSComponentNode *comp = static_cast<Q3DSComponentNode *>(object); + m_sceneManager->engine()->loadSlideResources(comp->masterSlide(), + m_sceneManager->presentation()); + m_sceneManager->engine()->loadSlideResources(comp->masterSlide(), + m_sceneManager->presentation()); + } + } qCDebug(lcSlidePlayer, "Setting internal state for %s from %s to %s", getSlideId(currentSlide).constData(), getEnumName(m_data.state), getEnumName(m_data.pendingState)); @@ -617,6 +642,17 @@ void Q3DSSlidePlayer::setInternalState(Q3DSSlidePlayer::PlayerState state) const bool forceUpdate = (!slideChanged && (state == PlayerState::Ready)); if (slideChanged || forceUpdate) handleCurrentSlideChanged(currentSlide, previousSlide, forceUpdate); + if (slideChanged) { + currentSlide->setActive(true); + m_sceneManager->engine()->loadSlideResources(currentSlide, m_sceneManager->presentation()); + if (previousSlide) { + previousSlide->setActive(false); + if (previousSlide->unloadSlide()) { + m_sceneManager->engine()->unloadSlideResources(previousSlide, + m_sceneManager->presentation()); + } + } + } if (state == PlayerState::Playing) { const bool restart = (m_mode == PlayerMode::Viewer) diff --git a/src/runtime/q3dsuippresentation.cpp b/src/runtime/q3dsuippresentation.cpp index 0e654c5..56f97a1 100644 --- a/src/runtime/q3dsuippresentation.cpp +++ b/src/runtime/q3dsuippresentation.cpp @@ -691,6 +691,20 @@ void Q3DSGraphObject::notifyPropertyChanges(int changeFlags, const QSet<QString> } } +void Q3DSGraphObject::listCustomPropertyResources( + QSet<QString> &resources, const QMap<QString, Q3DSMaterial::PropertyElement> &meta) const +{ + auto properties = dynamicProperties(); + for (auto it = properties.cbegin(), itEnd = properties.cend(); it != itEnd; ++it) { + const QString &propName(it.key()); + const Q3DSMaterial::PropertyElement &propMeta(meta[propName]); + if (propMeta.type == Q3DS::Texture) { + const QVariant &propValue(it.value()); + resources.insert(propValue.toString()); + } + } +} + // Setters return a change object that can be passed straight to // notifyPropertyChange (via the changelists' intializer list even). When the // value does not change, the returned change object has isValid()==false, these @@ -1646,6 +1660,16 @@ Q3DSPropertyChange Q3DSSlide::setPlayThroughValue(const QVariant &v) return createPropSetter(m_playThroughValue, v, "playthroughto"); } +void Q3DSSlide::generateResourceSet(Q3DSUipPresentation *presentation) +{ + m_resources.clear(); + QSet<QString> resources; + for (const auto *object : qAsConst(m_objects)) + object->listResources(resources); + for (auto res : qAsConst(resources)) + m_resources << presentation->imageUrl(res); +} + Q3DSImage::Q3DSImage() : Q3DSGraphObject(Image) { @@ -1721,6 +1745,12 @@ void Q3DSImage::resolveReferences(Q3DSUipPresentation &presentation) } } +void Q3DSImage::listResources(QSet<QString> &resources) const +{ + if (!m_sourcePath.isEmpty() && !resources.contains(m_sourcePath)) + resources.insert(m_sourcePath); +} + namespace { #if 0 bool scanImageForAlpha(const uchar *data, int width, int height, unsigned pixelSizeInBytes, unsigned alphaSizeInBits, bool isAlphaFirst = false) @@ -3936,6 +3966,24 @@ Q3DSGraphObject *Q3DSUipPresentation::getObjectByName(const QString &name) const return nullptr; } +QUrl Q3DSUipPresentation::imageUrl(const QString &sourcePath) +{ + QUrl ret; + if (d->preferKtx) { + QString ktxSource = sourcePath; + ktxSource = ktxSource.left(ktxSource.lastIndexOf(QLatin1Char('.'))); + ktxSource.append(QLatin1String(".ktx")); + QFileInfo info(ktxSource); + if (info.exists()) + ret = QUrl::fromLocalFile(ktxSource); + else + ret = QUrl::fromLocalFile(sourcePath); + } else { + ret = QUrl::fromLocalFile(sourcePath); + } + return ret; +} + namespace { struct ClonedObject { Q3DSGraphObject *original; diff --git a/src/runtime/q3dsuippresentation_p.h b/src/runtime/q3dsuippresentation_p.h index de1be1e..fde8233 100644 --- a/src/runtime/q3dsuippresentation_p.h +++ b/src/runtime/q3dsuippresentation_p.h @@ -406,8 +406,14 @@ public: State state() const { return m_state; } + virtual void listResources(QSet<QString> &resources) const + { + Q_UNUSED(resources); + } protected: void destroyGraph(); + void listCustomPropertyResources(QSet<QString> &resources, const QMap<QString, + Q3DSMaterial::PropertyElement> &meta) const; QByteArray m_id; QString m_name; @@ -739,6 +745,23 @@ public: Q3DSPropertyChange setPlayThrough(PlayThrough v); Q3DSPropertyChange setPlayThroughValue(const QVariant &v); + void generateResourceSet(Q3DSUipPresentation *presentation); + QSet<QUrl> resourceSet() const { return m_resources; } + + void setUnloadSlide(bool unload) + { + m_unload = unload; + } + void setActive(bool active) + { + m_active = active; + // The slide must be explicitly unloaded by the user every time + if (active) + m_unload = false; + } + bool unloadSlide() const { return m_unload; } + bool active() const { return m_active; } + private: Q_DISABLE_COPY(Q3DSSlide) template<typename V> void setProps(const V &attrs, PropSetFlags flags); @@ -756,6 +779,9 @@ private: QVector<Q3DSAction> m_actions; QVector<SlideGraphChangeCallback> m_slideGraphChangeCallbacks; // master only QVector<SlideObjectChangeCallback> m_slideObjectChangeCallbacks; + QSet<QUrl> m_resources; + bool m_unload = false; + bool m_active = false; friend class Q3DSUipParser; friend class Q3DSGraphObject; @@ -842,6 +868,8 @@ public: Q3DSPropertyChange setCustomImage(const QImage &v); // alternative setter QImage customImage() const { return m_customImage; } + void listResources(QSet<QString> &resources) const override; + private: Q_DISABLE_COPY(Q3DSImage) template<typename V> void setProps(const V &attrs, PropSetFlags flags); @@ -1956,6 +1984,10 @@ public: const Q3DSCustomMaterial *material() const { return &m_material; } + void listResources(QSet<QString> &resources) const override + { + listCustomPropertyResources(resources, m_material.properties()); + } private: Q_DISABLE_COPY(Q3DSCustomMaterialInstance) template<typename V> void setProps(const V &attrs, PropSetFlags flags); @@ -2005,6 +2037,11 @@ public: const Q3DSEffect *effect() const { return &m_effect; } + void listResources(QSet<QString> &resources) const override + { + listCustomPropertyResources(resources, m_effect.properties()); + } + private: Q_DISABLE_COPY(Q3DSEffectInstance) template<typename V> void setProps(const V &attrs, PropSetFlags flags); @@ -2200,6 +2237,8 @@ public: void addImplicitPropertyChanges(); QHash<QString, bool> &imageTransparencyHash(); + QUrl imageUrl(const QString &sourcePath); + private: Q_DISABLE_COPY(Q3DSUipPresentation) diff --git a/src/runtime/slideplayerng/q3dsslideplayerng.cpp b/src/runtime/slideplayerng/q3dsslideplayerng.cpp index ac24c30..854c72e 100644 --- a/src/runtime/slideplayerng/q3dsslideplayerng.cpp +++ b/src/runtime/slideplayerng/q3dsslideplayerng.cpp @@ -34,6 +34,7 @@ #include <QtCore/qmetaobject.h> #include "q3dsscenemanager_p.h" #include "q3dsanimationmanagerng_p.h" +#include "q3dsengine_p.h" #include "q3dslogging_p.h" #include "animator/q3dsanimator_p.h" @@ -390,6 +391,18 @@ void Q3DSSlidePlayerNg::setSlideDeck(Q3DSSlideDeck *slideDeck) }; forAllSlides(slideDeck); + m_sceneManager->engine()->loadSlideResources(slideDeck->masterSlide(), + m_sceneManager->presentation()); + for (auto object : slideDeck->masterSlide()->objects()) { + if (object->type() == Q3DSGraphObject::Component + && object->state() == Q3DSGraphObject::Enabled) { + Q3DSComponentNode *comp = static_cast<Q3DSComponentNode *>(object); + m_sceneManager->engine()->loadSlideResources(comp->masterSlide(), + m_sceneManager->presentation()); + m_sceneManager->engine()->loadSlideResources(comp->currentSlide(), + m_sceneManager->presentation()); + } + } setInternalState(PlayerState::Ready); Q_EMIT slideDeckChanged(m_data.slideDeck); } @@ -533,8 +546,28 @@ void Q3DSSlidePlayerNg::setInternalState(Q3DSSlidePlayerNg::PlayerState newState m_sceneManager->queueEvent(ev); }; + Q3DSSlide *oldSlide = m_currentSlide; Q3DSSlide *currentSlide = m_data.slideDeck->currentSlide(); Q_ASSERT(currentSlide); + m_sceneManager->engine()->loadSlideResources(currentSlide, m_sceneManager->presentation()); + if (m_currentSlide && m_currentSlide != currentSlide) { + m_currentSlide->setActive(false); + if (m_currentSlide->unloadSlide()) + m_sceneManager->engine()->unloadSlideResources(m_currentSlide, + m_sceneManager->presentation()); + } + m_currentSlide = currentSlide; + m_currentSlide->setActive(true); + for (auto object : currentSlide->objects()) { + if (object->type() == Q3DSGraphObject::Component + && object->state() == Q3DSGraphObject::Enabled) { + Q3DSComponentNode *comp = static_cast<Q3DSComponentNode *>(object); + m_sceneManager->engine()->loadSlideResources(comp->masterSlide(), + m_sceneManager->presentation()); + m_sceneManager->engine()->loadSlideResources(comp->currentSlide(), + m_sceneManager->presentation()); + } + } Q3DSSlide *activeSlide = (m_type == PlayerType::Scene) ? m_sceneManager->currentSlide() : m_component->currentSlide(); @@ -612,6 +645,8 @@ void Q3DSSlidePlayerNg::setInternalState(Q3DSSlidePlayerNg::PlayerState newState m_sceneManager->syncScene(); if (newState != PlayerState::Idle) { + if (oldSlide) + queueSlideEvent(oldSlide, Q3DSGraphObjectEvents::slideExitEvent()); queueSlideEvent(currentSlide, Q3DSGraphObjectEvents::slideEnterEvent()); Q_EMIT slideChanged(currentSlide); } diff --git a/src/runtime/slideplayerng/q3dsslideplayerng_p.h b/src/runtime/slideplayerng/q3dsslideplayerng_p.h index cdec113..b17991c 100644 --- a/src/runtime/slideplayerng/q3dsslideplayerng_p.h +++ b/src/runtime/slideplayerng/q3dsslideplayerng_p.h @@ -118,6 +118,7 @@ private: float playbackRate = 1.0f; } m_data; + Q3DSSlide *m_currentSlide = nullptr; Q3DSSceneManager *m_sceneManager; Q3DSComponentNode *m_component = nullptr; PlayerMode m_mode = PlayerMode::Viewer; |