1 /****************************************************************************
2 **
3 ** Copyright (C) 2008-2012 NVIDIA Corporation.
4 ** Copyright (C) 2019 The Qt Company Ltd.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of Qt Quick 3D.
8 **
9 ** $QT_BEGIN_LICENSE:GPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU
20 ** General Public License version 3 or (at your option) any later version
21 ** approved by the KDE Free Qt Foundation. The licenses are as published by
22 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
23 ** included in the packaging of this file. Please review the following
24 ** information to ensure the GNU General Public License requirements will
25 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
26 **
27 ** $QT_END_LICENSE$
28 **
29 ****************************************************************************/
30 
31 #include "qssgrenderimagebatchloader_p.h"
32 
33 #include <QtQuick3DUtils/private/qssginvasivelinkedlist_p.h>
34 
35 #include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
36 #include <QtQuick3DRuntimeRender/private/qssgrenderinputstreamfactory_p.h>
37 #include <QtQuick3DRuntimeRender/private/qssgrenderthreadpool_p.h>
38 #include <QtQuick3DRuntimeRender/private/qssgrenderloadedtexture_p.h>
39 
40 #include <QtCore/QMutex>
41 #include <QtCore/QWaitCondition>
42 
43 QT_BEGIN_NAMESPACE
44 
45 namespace {
46 
47 struct QSSGImageLoaderBatch;
48 
49 struct QSSGLoadingImage
50 {
51     QSSGImageLoaderBatch *batch = nullptr;
52     QString sourcePath;
53     quint64 taskId = 0;
54     QSSGLoadingImage *tail = nullptr;
55 
56     // Called from main thread
QSSGLoadingImage__anon7ff08b630111::QSSGLoadingImage57     QSSGLoadingImage(const QString &inSourcePath) : batch(nullptr), sourcePath(inSourcePath), taskId(0), tail(nullptr) {}
58 
59     QSSGLoadingImage() = default;
60 
61     // Called from main thread
62     void setup(QSSGImageLoaderBatch &inBatch);
63 
64     // Called from loader thread
65     static void loadImage(void *inImg);
66 
67     // Potentially called from loader thread
68     static void taskCancelled(void *inImg);
69 };
70 
71 using TLoadingImageList = QSSGInvasiveSingleLinkedList<QSSGLoadingImage, &QSSGLoadingImage::tail>;
72 
73 struct QSSGBatchLoader;
74 
75 struct QSSGImageLoaderBatch
76 {
77     // All variables setup in main thread and constant from then on except
78     // loaded image count.
79     QSSGBatchLoader &loader;
80     QSSGRef<IImageLoadListener> loadListener;
81     QWaitCondition loadEvent;
82     QMutex loadMutex;
83     TLoadingImageList images;
84 
85     TImageBatchId batchId;
86     // Incremented in main thread
87     quint32 loadedOrCanceledImageCount;
88     quint32 finalizedImageCount;
89     quint32 numImages;
90     QSSGRenderContextType contextType;
91 
92     // Called from main thread
93     static QSSGImageLoaderBatch *createLoaderBatch(QSSGBatchLoader &inLoader,
94                                                    TImageBatchId inBatchId,
95                                                    QSSGDataView<QString> inSourcePaths,
96                                                    const QString &inImageTillLoaded,
97                                                    IImageLoadListener *inListener,
98                                                    QSSGRenderContextType contextType);
99 
100     // Called from main thread
101     QSSGImageLoaderBatch(QSSGBatchLoader &inLoader,
102                            IImageLoadListener *inLoadListener,
103                            const TLoadingImageList &inImageList,
104                            TImageBatchId inBatchId,
105                            quint32 inImageCount,
106                            QSSGRenderContextType contextType);
107 
108     // Called from main thread
109     ~QSSGImageLoaderBatch();
110 
111     // Called from main thread
isLoadingFinished__anon7ff08b630111::QSSGImageLoaderBatch112     bool isLoadingFinished()
113     {
114         QMutexLocker locker(&loadMutex);
115         return loadedOrCanceledImageCount >= numImages;
116     }
117 
isFinalizedFinished__anon7ff08b630111::QSSGImageLoaderBatch118     bool isFinalizedFinished()
119     {
120         QMutexLocker locker(&loadMutex);
121         return finalizedImageCount >= numImages;
122     }
123 
incrementLoadedImageCount__anon7ff08b630111::QSSGImageLoaderBatch124     void incrementLoadedImageCount()
125     {
126         QMutexLocker locker(&loadMutex);
127         ++loadedOrCanceledImageCount;
128     }
incrementFinalizedImageCount__anon7ff08b630111::QSSGImageLoaderBatch129     void incrementFinalizedImageCount()
130     {
131         QMutexLocker locker(&loadMutex);
132         ++finalizedImageCount;
133     }
134     // Called from main thread
135     void cancel();
136     void cancel(const QString &inSourcePath);
137 };
138 
139 struct QSSGBatchLoadedImage
140 {
141     QString sourcePath;
142     QSSGRef<QSSGLoadedTexture> texture;
143     QSSGImageLoaderBatch *batch = nullptr;
144     QSSGBatchLoadedImage() = default;
145 
146     // Called from loading thread
QSSGBatchLoadedImage__anon7ff08b630111::QSSGBatchLoadedImage147     QSSGBatchLoadedImage(const QString &inSourcePath, QSSGLoadedTexture *inTexture, QSSGImageLoaderBatch &inBatch)
148         : sourcePath(inSourcePath), texture(inTexture), batch(&inBatch)
149     {
150     }
151 
152     // Called from main thread
153     bool finalize(const QSSGRef<QSSGBufferManager> &inMgr);
154 };
155 
156 struct QSSGBatchLoader : public IImageBatchLoader
157 {
158     typedef QHash<TImageBatchId, QSSGImageLoaderBatch *> TImageLoaderBatchMap;
159     typedef QHash<QString, TImageBatchId> TSourcePathToBatchMap;
160 
161     // Accessed from loader thread
162     QSSGRef<QSSGInputStreamFactory> inputStreamFactory;
163     //!!Not threadsafe!  accessed only from main thread
164     QSSGRef<QSSGBufferManager> bufferManager;
165     // Accessed from main thread
166     QSSGRef<QSSGAbstractThreadPool> threadPool;
167     // Accessed from both threads
168     QSSGPerfTimer *perfTimer;
169     // main thread
170     TImageBatchId nextBatchId;
171     // main thread
172     TImageLoaderBatchMap batches;
173     // main thread
174     QMutex loaderMutex;
175 
176     // Both loader and main threads
177     QVector<QSSGBatchLoadedImage> loadedImages;
178     // main thread
179     QVector<TImageBatchId> finishedBatches;
180     // main thread
181     TSourcePathToBatchMap sourcePathToBatches;
182     // main thread
183     QVector<QSSGLoadingImage> loaderBuilderWorkspace;
184 
QSSGBatchLoader__anon7ff08b630111::QSSGBatchLoader185     QSSGBatchLoader(const QSSGRef<QSSGInputStreamFactory> &inFactory,
186                     const QSSGRef<QSSGBufferManager> &inBufferManager,
187                     const QSSGRef<QSSGAbstractThreadPool> &inThreadPool,
188                     QSSGPerfTimer *inTimer)
189         : inputStreamFactory(inFactory), bufferManager(inBufferManager), threadPool(inThreadPool), perfTimer(inTimer), nextBatchId(1)
190     {
191     }
192 
193     virtual ~QSSGBatchLoader() override;
194 
195     // Returns an ID to the load request.  Request a block of images to be loaded.
196     // Also takes an image that the buffer system will return when requested for the given source
197     // paths
198     // until said path is loaded.
199     // An optional listener can be passed in to get callbacks about the batch.
loadImageBatch__anon7ff08b630111::QSSGBatchLoader200     TImageBatchId loadImageBatch(QSSGDataView<QString> inSourcePaths,
201                                  QString inImageTillLoaded,
202                                  IImageLoadListener *inListener,
203                                  QSSGRenderContextType contextType) override
204     {
205         if (inSourcePaths.size() == 0)
206             return 0;
207 
208         QMutexLocker loaderLock(&loaderMutex);
209 
210         TImageBatchId theBatchId = 0;
211 
212         // Empty loop intentional to find an unused batch id.
213         for (theBatchId = nextBatchId; batches.find(theBatchId) != batches.end(); ++nextBatchId, theBatchId = nextBatchId) {
214         }
215 
216         QSSGImageLoaderBatch *theBatch(
217                 QSSGImageLoaderBatch::createLoaderBatch(*this, theBatchId, inSourcePaths, inImageTillLoaded, inListener, contextType));
218         if (theBatch) {
219             batches.insert(theBatchId, theBatch);
220             return theBatchId;
221         }
222         return 0;
223     }
224 
cancelImageBatchLoading__anon7ff08b630111::QSSGBatchLoader225     void cancelImageBatchLoading(TImageBatchId inBatchId) override
226     {
227         QSSGImageLoaderBatch *theBatch(getBatch(inBatchId));
228         if (theBatch)
229             theBatch->cancel();
230     }
231 
232     // Blocks if the image is currently in-flight
cancelImageLoading__anon7ff08b630111::QSSGBatchLoader233     void cancelImageLoading(QString inSourcePath) override
234     {
235         QMutexLocker loaderLock(&loaderMutex);
236         TSourcePathToBatchMap::iterator theIter = sourcePathToBatches.find(inSourcePath);
237         if (theIter != sourcePathToBatches.end()) {
238             TImageBatchId theBatchId = theIter.value();
239             TImageLoaderBatchMap::iterator theBatchIter = batches.find(theBatchId);
240             if (theBatchIter != batches.end())
241                 theBatchIter.value()->cancel(inSourcePath);
242         }
243     }
244 
getBatch__anon7ff08b630111::QSSGBatchLoader245     QSSGImageLoaderBatch *getBatch(TImageBatchId inId)
246     {
247         QMutexLocker loaderLock(&loaderMutex);
248         TImageLoaderBatchMap::iterator theIter = batches.find(inId);
249         if (theIter != batches.end())
250             return theIter.value();
251         return nullptr;
252     }
253 
blockUntilLoaded__anon7ff08b630111::QSSGBatchLoader254     void blockUntilLoaded(TImageBatchId inId) override
255     {
256         // TODO: This is not sane
257         QMutexLocker locker(&loaderMutex);
258         for (QSSGImageLoaderBatch *theBatch = getBatch(inId); theBatch; theBatch = getBatch(inId)) {
259             // Only need to block if images aren't loaded.  Don't need to block if they aren't
260             // finalized.
261             if (!theBatch->isLoadingFinished()) {
262                 theBatch->loadEvent.wait(&loaderMutex, 200);
263                 //                theBatch->m_LoadEvent.reset(); ???
264             }
265             beginFrame();
266         }
267     }
imageLoaded__anon7ff08b630111::QSSGBatchLoader268     void imageLoaded(QSSGLoadingImage &inImage, QSSGLoadedTexture *inTexture)
269     {
270         QMutexLocker loaderLock(&loaderMutex);
271         loadedImages.push_back(QSSGBatchLoadedImage(inImage.sourcePath, inTexture, *inImage.batch));
272         inImage.batch->incrementLoadedImageCount();
273         inImage.batch->loadEvent.wakeAll();
274     }
275     // These are called by the render context, users don't need to call this.
beginFrame__anon7ff08b630111::QSSGBatchLoader276     void beginFrame() override
277     {
278         QMutexLocker loaderLock(&loaderMutex);
279         // Pass 1 - send out all image loaded signals
280         for (int idx = 0, end = loadedImages.size(); idx < end; ++idx) {
281 
282             sourcePathToBatches.remove(loadedImages[idx].sourcePath);
283             loadedImages[idx].finalize(bufferManager);
284             loadedImages[idx].batch->incrementFinalizedImageCount();
285             if (loadedImages[idx].batch->isFinalizedFinished())
286                 finishedBatches.push_back(loadedImages[idx].batch->batchId);
287         }
288         loadedImages.clear();
289         // pass 2 - clean up any existing batches.
290         for (int idx = 0, end = finishedBatches.size(); idx < end; ++idx) {
291             TImageLoaderBatchMap::iterator theIter = batches.find(finishedBatches[idx]);
292             if (theIter != batches.end()) {
293                 QSSGImageLoaderBatch *theBatch = theIter.value();
294                 if (theBatch->loadListener)
295                     theBatch->loadListener->OnImageBatchComplete(theBatch->batchId);
296                 batches.remove(finishedBatches[idx]);
297                 // TODO:
298                 theBatch->~QSSGImageLoaderBatch();
299             }
300         }
301         finishedBatches.clear();
302     }
303 
endFrame__anon7ff08b630111::QSSGBatchLoader304     void endFrame() override {}
305 };
306 
setup(QSSGImageLoaderBatch & inBatch)307 void QSSGLoadingImage::setup(QSSGImageLoaderBatch &inBatch)
308 {
309     batch = &inBatch;
310     taskId = inBatch.loader.threadPool->addTask(this, loadImage, taskCancelled);
311 }
312 
loadImage(void * inImg)313 void QSSGLoadingImage::loadImage(void *inImg)
314 {
315     QSSGLoadingImage *theThis = reinterpret_cast<QSSGLoadingImage *>(inImg);
316     //    SStackPerfTimer theTimer(theThis->m_Batch->m_Loader.m_PerfTimer, "Image Decompression");
317     if (theThis->batch->loader.bufferManager->isImageLoaded(theThis->sourcePath) == false) {
318         QSSGRef<QSSGLoadedTexture> theTexture = QSSGLoadedTexture::load(theThis->sourcePath,
319                                                                               QSSGRenderTextureFormat::Unknown,
320                                                                               *theThis->batch->loader.inputStreamFactory,
321                                                                               true,
322                                                                               theThis->batch->contextType);
323         // if ( theTexture )
324         //	theTexture->EnsureMultiplerOfFour( theThis->m_Batch->m_Loader.m_Foundation,
325         // theThis->m_SourcePath.c_str() );
326 
327         theThis->batch->loader.imageLoaded(*theThis, theTexture.data());
328     } else {
329         theThis->batch->loader.imageLoaded(*theThis, nullptr);
330     }
331 }
332 
taskCancelled(void * inImg)333 void QSSGLoadingImage::taskCancelled(void *inImg)
334 {
335     QSSGLoadingImage *theThis = reinterpret_cast<QSSGLoadingImage *>(inImg);
336     theThis->batch->loader.imageLoaded(*theThis, nullptr);
337 }
338 
finalize(const QSSGRef<QSSGBufferManager> & inMgr)339 bool QSSGBatchLoadedImage::finalize(const QSSGRef<QSSGBufferManager> &inMgr)
340 {
341     if (texture) {
342         // PKC : We'll look at the path location to see if the image is in the standard
343         // location for IBL light probes or a standard hdr format and decide to generate BSDF
344         // miplevels (if the image doesn't have
345         // mipmaps of its own that is).
346         QString thepath(sourcePath);
347         bool isIBL = (thepath.contains(QLatin1String(".hdr"))) || (thepath.contains(QLatin1String("\\IBL\\"))) ||
348                      (thepath.contains(QLatin1String("/IBL/")));
349         inMgr->loadRenderImage(sourcePath, texture, false, isIBL);
350         inMgr->unaliasImagePath(sourcePath);
351     }
352     if (batch->loadListener)
353         batch->loadListener->OnImageLoadComplete(sourcePath, texture ? ImageLoadResult::Succeeded : ImageLoadResult::Failed);
354 
355     if (texture) {
356         // m_Texture->release();
357         return true;
358     }
359 
360     return false;
361 }
362 
createLoaderBatch(QSSGBatchLoader & inLoader,TImageBatchId inBatchId,QSSGDataView<QString> inSourcePaths,const QString & inImageTillLoaded,IImageLoadListener * inListener,QSSGRenderContextType contextType)363 QSSGImageLoaderBatch *QSSGImageLoaderBatch::createLoaderBatch(QSSGBatchLoader &inLoader,
364                                                               TImageBatchId inBatchId,
365                                                               QSSGDataView<QString> inSourcePaths,
366                                                               const QString &inImageTillLoaded,
367                                                               IImageLoadListener *inListener,
368                                                               QSSGRenderContextType contextType)
369 {
370     TLoadingImageList theImages;
371     quint32 theLoadingImageCount = 0;
372     for (int idx = 0, end = inSourcePaths.size(); idx < end; ++idx) {
373         QString theSourcePath(inSourcePaths[idx]);
374 
375         // TODO: What was the meaning of isValid() (now isEmpty())??
376         if (theSourcePath.isEmpty() == false)
377             continue;
378 
379         if (inLoader.bufferManager->isImageLoaded(theSourcePath))
380             continue;
381 
382         const auto foundIt = inLoader.sourcePathToBatches.find(inSourcePaths[idx]);
383 
384         // TODO: This is a bit funky, check if we really need to update the inBatchId...
385         inLoader.sourcePathToBatches.insert(inSourcePaths[idx], inBatchId);
386 
387         // If the loader has already seen this image.
388         if (foundIt != inLoader.sourcePathToBatches.constEnd())
389             continue;
390 
391         if (inImageTillLoaded.isEmpty()) {
392             // Alias the image so any further requests for this source path will result in
393             // the default images (image till loaded).
394             bool aliasSuccess = inLoader.bufferManager->aliasImagePath(theSourcePath, inImageTillLoaded, true);
395             (void)aliasSuccess;
396             Q_ASSERT(aliasSuccess);
397         }
398 
399         // TODO: Yeah... make sure this is cleaned up correctly.
400         QSSGLoadingImage *sli = new QSSGLoadingImage(theSourcePath);
401         theImages.push_front(*sli);
402         ++theLoadingImageCount;
403     }
404     if (theImages.empty() == false) {
405         QSSGImageLoaderBatch *theBatch = new QSSGImageLoaderBatch(inLoader, inListener, theImages, inBatchId, theLoadingImageCount, contextType);
406         return theBatch;
407     }
408     return nullptr;
409 }
410 
QSSGImageLoaderBatch(QSSGBatchLoader & inLoader,IImageLoadListener * inLoadListener,const TLoadingImageList & inImageList,TImageBatchId inBatchId,quint32 inImageCount,QSSGRenderContextType contextType)411 QSSGImageLoaderBatch::QSSGImageLoaderBatch(QSSGBatchLoader &inLoader,
412                                                IImageLoadListener *inLoadListener,
413                                                const TLoadingImageList &inImageList,
414                                                TImageBatchId inBatchId,
415                                                quint32 inImageCount,
416                                                QSSGRenderContextType contextType)
417     : loader(inLoader)
418     , loadListener(inLoadListener)
419     , images(inImageList)
420     , batchId(inBatchId)
421     , loadedOrCanceledImageCount(0)
422     , finalizedImageCount(0)
423     , numImages(inImageCount)
424     , contextType(contextType)
425 {
426     for (TLoadingImageList::iterator iter = images.begin(), end = images.end(); iter != end; ++iter) {
427         iter->setup(*this);
428     }
429 }
430 
~QSSGImageLoaderBatch()431 QSSGImageLoaderBatch::~QSSGImageLoaderBatch()
432 {
433     auto iter = images.begin();
434     const auto end = images.end();
435     while (iter != end) {
436         auto temp(iter);
437         ++iter;
438         delete temp.m_obj;
439     }
440 }
441 
cancel()442 void QSSGImageLoaderBatch::cancel()
443 {
444     for (TLoadingImageList::iterator iter = images.begin(), end = images.end(); iter != end; ++iter)
445         loader.threadPool->cancelTask(iter->taskId);
446 }
447 
cancel(const QString & inSourcePath)448 void QSSGImageLoaderBatch::cancel(const QString &inSourcePath)
449 {
450     for (TLoadingImageList::iterator iter = images.begin(), end = images.end(); iter != end; ++iter) {
451         if (iter->sourcePath == inSourcePath) {
452             loader.threadPool->cancelTask(iter->taskId);
453             break;
454         }
455     }
456 }
457 }
458 
createBatchLoader(const QSSGRef<QSSGInputStreamFactory> & inFactory,const QSSGRef<QSSGBufferManager> & inBufferManager,const QSSGRef<QSSGAbstractThreadPool> & inThreadPool,QSSGPerfTimer * inTimer)459 QSSGRef<IImageBatchLoader> IImageBatchLoader::createBatchLoader(const QSSGRef<QSSGInputStreamFactory> &inFactory,
460                                                                 const QSSGRef<QSSGBufferManager> &inBufferManager,
461                                                                 const QSSGRef<QSSGAbstractThreadPool> &inThreadPool,
462                                                                 QSSGPerfTimer *inTimer)
463 {
464     return QSSGRef<IImageBatchLoader>(new QSSGBatchLoader(inFactory, inBufferManager, inThreadPool, inTimer));
465 }
466 
~QSSGBatchLoader()467 QSSGBatchLoader::~QSSGBatchLoader()
468 {
469     QVector<TImageBatchId> theCancelledBatches;
470     auto theIter = batches.begin();
471     const auto theEnd = batches.end();
472     while (theIter != theEnd) {
473         theIter.value()->cancel();
474         theCancelledBatches.push_back(theIter.value()->batchId);
475         ++theIter;
476     }
477     for (int idx = 0, end = theCancelledBatches.size(); idx < end; ++idx)
478         blockUntilLoaded(theCancelledBatches[idx]);
479 
480     Q_ASSERT(batches.size() == 0);
481 }
482 
483 QT_END_NAMESPACE
484