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