blob: 841e976e7c7930e15b6ac26fdd12b15aebbdbb98 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2008-2012 NVIDIA Corporation.
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qssgrenderimagebatchloader_p.h"
#include <QtQuick3DUtils/private/qssginvasivelinkedlist_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderinputstreamfactory_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderthreadpool_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderloadedtexture_p.h>
#include <QtCore/QMutex>
#include <QtCore/QWaitCondition>
QT_BEGIN_NAMESPACE
namespace {
struct QSSGImageLoaderBatch;
struct QSSGLoadingImage
{
QSSGImageLoaderBatch *batch = nullptr;
QString sourcePath;
quint64 taskId = 0;
QSSGLoadingImage *tail = nullptr;
// Called from main thread
QSSGLoadingImage(const QString &inSourcePath) : batch(nullptr), sourcePath(inSourcePath), taskId(0), tail(nullptr) {}
QSSGLoadingImage() = default;
// Called from main thread
void setup(QSSGImageLoaderBatch &inBatch);
// Called from loader thread
static void loadImage(void *inImg);
// Potentially called from loader thread
static void taskCancelled(void *inImg);
};
using TLoadingImageList = QSSGInvasiveSingleLinkedList<QSSGLoadingImage, &QSSGLoadingImage::tail>;
struct QSSGBatchLoader;
struct QSSGImageLoaderBatch
{
// All variables setup in main thread and constant from then on except
// loaded image count.
QSSGBatchLoader &loader;
QSSGRef<IImageLoadListener> loadListener;
QWaitCondition loadEvent;
QMutex loadMutex;
TLoadingImageList images;
TImageBatchId batchId;
// Incremented in main thread
quint32 loadedOrCanceledImageCount;
quint32 finalizedImageCount;
quint32 numImages;
QSSGRenderContextType contextType;
// Called from main thread
static QSSGImageLoaderBatch *createLoaderBatch(QSSGBatchLoader &inLoader,
TImageBatchId inBatchId,
QSSGDataView<QString> inSourcePaths,
const QString &inImageTillLoaded,
IImageLoadListener *inListener,
QSSGRenderContextType contextType);
// Called from main thread
QSSGImageLoaderBatch(QSSGBatchLoader &inLoader,
IImageLoadListener *inLoadListener,
const TLoadingImageList &inImageList,
TImageBatchId inBatchId,
quint32 inImageCount,
QSSGRenderContextType contextType);
// Called from main thread
~QSSGImageLoaderBatch();
// Called from main thread
bool isLoadingFinished()
{
QMutexLocker locker(&loadMutex);
return loadedOrCanceledImageCount >= numImages;
}
bool isFinalizedFinished()
{
QMutexLocker locker(&loadMutex);
return finalizedImageCount >= numImages;
}
void incrementLoadedImageCount()
{
QMutexLocker locker(&loadMutex);
++loadedOrCanceledImageCount;
}
void incrementFinalizedImageCount()
{
QMutexLocker locker(&loadMutex);
++finalizedImageCount;
}
// Called from main thread
void cancel();
void cancel(const QString &inSourcePath);
};
struct QSSGBatchLoadedImage
{
QString sourcePath;
QSSGRef<QSSGLoadedTexture> texture;
QSSGImageLoaderBatch *batch = nullptr;
QSSGBatchLoadedImage() = default;
// Called from loading thread
QSSGBatchLoadedImage(const QString &inSourcePath, QSSGLoadedTexture *inTexture, QSSGImageLoaderBatch &inBatch)
: sourcePath(inSourcePath), texture(inTexture), batch(&inBatch)
{
}
// Called from main thread
bool finalize(const QSSGRef<QSSGBufferManager> &inMgr);
};
struct QSSGBatchLoader : public IImageBatchLoader
{
typedef QHash<TImageBatchId, QSSGImageLoaderBatch *> TImageLoaderBatchMap;
typedef QHash<QString, TImageBatchId> TSourcePathToBatchMap;
// Accessed from loader thread
QSSGRef<QSSGInputStreamFactory> inputStreamFactory;
//!!Not threadsafe! accessed only from main thread
QSSGRef<QSSGBufferManager> bufferManager;
// Accessed from main thread
QSSGRef<QSSGAbstractThreadPool> threadPool;
// Accessed from both threads
QSSGPerfTimer *perfTimer;
// main thread
TImageBatchId nextBatchId;
// main thread
TImageLoaderBatchMap batches;
// main thread
QMutex loaderMutex;
// Both loader and main threads
QVector<QSSGBatchLoadedImage> loadedImages;
// main thread
QVector<TImageBatchId> finishedBatches;
// main thread
TSourcePathToBatchMap sourcePathToBatches;
// main thread
QVector<QSSGLoadingImage> loaderBuilderWorkspace;
QSSGBatchLoader(const QSSGRef<QSSGInputStreamFactory> &inFactory,
const QSSGRef<QSSGBufferManager> &inBufferManager,
const QSSGRef<QSSGAbstractThreadPool> &inThreadPool,
QSSGPerfTimer *inTimer)
: inputStreamFactory(inFactory), bufferManager(inBufferManager), threadPool(inThreadPool), perfTimer(inTimer), nextBatchId(1)
{
}
virtual ~QSSGBatchLoader() override;
// Returns an ID to the load request. Request a block of images to be loaded.
// Also takes an image that the buffer system will return when requested for the given source
// paths
// until said path is loaded.
// An optional listener can be passed in to get callbacks about the batch.
TImageBatchId loadImageBatch(QSSGDataView<QString> inSourcePaths,
QString inImageTillLoaded,
IImageLoadListener *inListener,
QSSGRenderContextType contextType) override
{
if (inSourcePaths.size() == 0)
return 0;
QMutexLocker loaderLock(&loaderMutex);
TImageBatchId theBatchId = 0;
// Empty loop intentional to find an unused batch id.
for (theBatchId = nextBatchId; batches.find(theBatchId) != batches.end(); ++nextBatchId, theBatchId = nextBatchId) {
}
QSSGImageLoaderBatch *theBatch(
QSSGImageLoaderBatch::createLoaderBatch(*this, theBatchId, inSourcePaths, inImageTillLoaded, inListener, contextType));
if (theBatch) {
batches.insert(theBatchId, theBatch);
return theBatchId;
}
return 0;
}
void cancelImageBatchLoading(TImageBatchId inBatchId) override
{
QSSGImageLoaderBatch *theBatch(getBatch(inBatchId));
if (theBatch)
theBatch->cancel();
}
// Blocks if the image is currently in-flight
void cancelImageLoading(QString inSourcePath) override
{
QMutexLocker loaderLock(&loaderMutex);
TSourcePathToBatchMap::iterator theIter = sourcePathToBatches.find(inSourcePath);
if (theIter != sourcePathToBatches.end()) {
TImageBatchId theBatchId = theIter.value();
TImageLoaderBatchMap::iterator theBatchIter = batches.find(theBatchId);
if (theBatchIter != batches.end())
theBatchIter.value()->cancel(inSourcePath);
}
}
QSSGImageLoaderBatch *getBatch(TImageBatchId inId)
{
QMutexLocker loaderLock(&loaderMutex);
TImageLoaderBatchMap::iterator theIter = batches.find(inId);
if (theIter != batches.end())
return theIter.value();
return nullptr;
}
void blockUntilLoaded(TImageBatchId inId) override
{
// TODO: This is not sane
QMutexLocker locker(&loaderMutex);
for (QSSGImageLoaderBatch *theBatch = getBatch(inId); theBatch; theBatch = getBatch(inId)) {
// Only need to block if images aren't loaded. Don't need to block if they aren't
// finalized.
if (!theBatch->isLoadingFinished()) {
theBatch->loadEvent.wait(&loaderMutex, 200);
// theBatch->m_LoadEvent.reset(); ???
}
beginFrame();
}
}
void imageLoaded(QSSGLoadingImage &inImage, QSSGLoadedTexture *inTexture)
{
QMutexLocker loaderLock(&loaderMutex);
loadedImages.push_back(QSSGBatchLoadedImage(inImage.sourcePath, inTexture, *inImage.batch));
inImage.batch->incrementLoadedImageCount();
inImage.batch->loadEvent.wakeAll();
}
// These are called by the render context, users don't need to call this.
void beginFrame() override
{
QMutexLocker loaderLock(&loaderMutex);
// Pass 1 - send out all image loaded signals
for (int idx = 0, end = loadedImages.size(); idx < end; ++idx) {
sourcePathToBatches.remove(loadedImages[idx].sourcePath);
loadedImages[idx].finalize(bufferManager);
loadedImages[idx].batch->incrementFinalizedImageCount();
if (loadedImages[idx].batch->isFinalizedFinished())
finishedBatches.push_back(loadedImages[idx].batch->batchId);
}
loadedImages.clear();
// pass 2 - clean up any existing batches.
for (int idx = 0, end = finishedBatches.size(); idx < end; ++idx) {
TImageLoaderBatchMap::iterator theIter = batches.find(finishedBatches[idx]);
if (theIter != batches.end()) {
QSSGImageLoaderBatch *theBatch = theIter.value();
if (theBatch->loadListener)
theBatch->loadListener->OnImageBatchComplete(theBatch->batchId);
batches.remove(finishedBatches[idx]);
// TODO:
theBatch->~QSSGImageLoaderBatch();
}
}
finishedBatches.clear();
}
void endFrame() override {}
};
void QSSGLoadingImage::setup(QSSGImageLoaderBatch &inBatch)
{
batch = &inBatch;
taskId = inBatch.loader.threadPool->addTask(this, loadImage, taskCancelled);
}
void QSSGLoadingImage::loadImage(void *inImg)
{
QSSGLoadingImage *theThis = reinterpret_cast<QSSGLoadingImage *>(inImg);
// SStackPerfTimer theTimer(theThis->m_Batch->m_Loader.m_PerfTimer, "Image Decompression");
if (theThis->batch->loader.bufferManager->isImageLoaded(theThis->sourcePath) == false) {
QSSGRef<QSSGLoadedTexture> theTexture = QSSGLoadedTexture::load(theThis->sourcePath,
QSSGRenderTextureFormat::Unknown,
*theThis->batch->loader.inputStreamFactory,
true,
theThis->batch->contextType);
// if ( theTexture )
// theTexture->EnsureMultiplerOfFour( theThis->m_Batch->m_Loader.m_Foundation,
// theThis->m_SourcePath.c_str() );
theThis->batch->loader.imageLoaded(*theThis, theTexture.data());
} else {
theThis->batch->loader.imageLoaded(*theThis, nullptr);
}
}
void QSSGLoadingImage::taskCancelled(void *inImg)
{
QSSGLoadingImage *theThis = reinterpret_cast<QSSGLoadingImage *>(inImg);
theThis->batch->loader.imageLoaded(*theThis, nullptr);
}
bool QSSGBatchLoadedImage::finalize(const QSSGRef<QSSGBufferManager> &inMgr)
{
if (texture) {
// PKC : We'll look at the path location to see if the image is in the standard
// location for IBL light probes or a standard hdr format and decide to generate BSDF
// miplevels (if the image doesn't have
// mipmaps of its own that is).
QString thepath(sourcePath);
bool isIBL = (thepath.contains(QLatin1String(".hdr"))) || (thepath.contains(QLatin1String("\\IBL\\"))) ||
(thepath.contains(QLatin1String("/IBL/")));
inMgr->loadRenderImage(sourcePath, texture, false, isIBL);
inMgr->unaliasImagePath(sourcePath);
}
if (batch->loadListener)
batch->loadListener->OnImageLoadComplete(sourcePath, texture ? ImageLoadResult::Succeeded : ImageLoadResult::Failed);
if (texture) {
// m_Texture->release();
return true;
}
return false;
}
QSSGImageLoaderBatch *QSSGImageLoaderBatch::createLoaderBatch(QSSGBatchLoader &inLoader,
TImageBatchId inBatchId,
QSSGDataView<QString> inSourcePaths,
const QString &inImageTillLoaded,
IImageLoadListener *inListener,
QSSGRenderContextType contextType)
{
TLoadingImageList theImages;
quint32 theLoadingImageCount = 0;
for (int idx = 0, end = inSourcePaths.size(); idx < end; ++idx) {
QString theSourcePath(inSourcePaths[idx]);
// TODO: What was the meaning of isValid() (now isEmpty())??
if (theSourcePath.isEmpty() == false)
continue;
if (inLoader.bufferManager->isImageLoaded(theSourcePath))
continue;
const auto foundIt = inLoader.sourcePathToBatches.find(inSourcePaths[idx]);
// TODO: This is a bit funky, check if we really need to update the inBatchId...
inLoader.sourcePathToBatches.insert(inSourcePaths[idx], inBatchId);
// If the loader has already seen this image.
if (foundIt != inLoader.sourcePathToBatches.constEnd())
continue;
if (inImageTillLoaded.isEmpty()) {
// Alias the image so any further requests for this source path will result in
// the default images (image till loaded).
bool aliasSuccess = inLoader.bufferManager->aliasImagePath(theSourcePath, inImageTillLoaded, true);
(void)aliasSuccess;
Q_ASSERT(aliasSuccess);
}
// TODO: Yeah... make sure this is cleaned up correctly.
QSSGLoadingImage *sli = new QSSGLoadingImage(theSourcePath);
theImages.push_front(*sli);
++theLoadingImageCount;
}
if (theImages.empty() == false) {
QSSGImageLoaderBatch *theBatch = new QSSGImageLoaderBatch(inLoader, inListener, theImages, inBatchId, theLoadingImageCount, contextType);
return theBatch;
}
return nullptr;
}
QSSGImageLoaderBatch::QSSGImageLoaderBatch(QSSGBatchLoader &inLoader,
IImageLoadListener *inLoadListener,
const TLoadingImageList &inImageList,
TImageBatchId inBatchId,
quint32 inImageCount,
QSSGRenderContextType contextType)
: loader(inLoader)
, loadListener(inLoadListener)
, images(inImageList)
, batchId(inBatchId)
, loadedOrCanceledImageCount(0)
, finalizedImageCount(0)
, numImages(inImageCount)
, contextType(contextType)
{
for (TLoadingImageList::iterator iter = images.begin(), end = images.end(); iter != end; ++iter) {
iter->setup(*this);
}
}
QSSGImageLoaderBatch::~QSSGImageLoaderBatch()
{
auto iter = images.begin();
const auto end = images.end();
while (iter != end) {
auto temp(iter);
++iter;
delete temp.m_obj;
}
}
void QSSGImageLoaderBatch::cancel()
{
for (TLoadingImageList::iterator iter = images.begin(), end = images.end(); iter != end; ++iter)
loader.threadPool->cancelTask(iter->taskId);
}
void QSSGImageLoaderBatch::cancel(const QString &inSourcePath)
{
for (TLoadingImageList::iterator iter = images.begin(), end = images.end(); iter != end; ++iter) {
if (iter->sourcePath == inSourcePath) {
loader.threadPool->cancelTask(iter->taskId);
break;
}
}
}
}
QSSGRef<IImageBatchLoader> IImageBatchLoader::createBatchLoader(const QSSGRef<QSSGInputStreamFactory> &inFactory,
const QSSGRef<QSSGBufferManager> &inBufferManager,
const QSSGRef<QSSGAbstractThreadPool> &inThreadPool,
QSSGPerfTimer *inTimer)
{
return QSSGRef<IImageBatchLoader>(new QSSGBatchLoader(inFactory, inBufferManager, inThreadPool, inTimer));
}
QSSGBatchLoader::~QSSGBatchLoader()
{
QVector<TImageBatchId> theCancelledBatches;
auto theIter = batches.begin();
const auto theEnd = batches.end();
while (theIter != theEnd) {
theIter.value()->cancel();
theCancelledBatches.push_back(theIter.value()->batchId);
++theIter;
}
for (int idx = 0, end = theCancelledBatches.size(); idx < end; ++idx)
blockUntilLoaded(theCancelledBatches[idx]);
Q_ASSERT(batches.size() == 0);
}
QT_END_NAMESPACE