Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsmaprendererparalleljob.cpp
3 : : --------------------------------------
4 : : Date : December 2013
5 : : Copyright : (C) 2013 by Martin Dobias
6 : : Email : wonder dot sk at gmail dot com
7 : : ***************************************************************************
8 : : * *
9 : : * This program is free software; you can redistribute it and/or modify *
10 : : * it under the terms of the GNU General Public License as published by *
11 : : * the Free Software Foundation; either version 2 of the License, or *
12 : : * (at your option) any later version. *
13 : : * *
14 : : ***************************************************************************/
15 : :
16 : : #include "qgsmaprendererparalleljob.h"
17 : :
18 : : #include "qgsfeedback.h"
19 : : #include "qgslabelingengine.h"
20 : : #include "qgslogger.h"
21 : : #include "qgsmaplayerrenderer.h"
22 : : #include "qgsproject.h"
23 : : #include "qgsmaplayer.h"
24 : : #include "qgsmaplayerlistutils.h"
25 : :
26 : : #include <QtConcurrentMap>
27 : : #include <QtConcurrentRun>
28 : :
29 : 0 : QgsMapRendererParallelJob::QgsMapRendererParallelJob( const QgsMapSettings &settings )
30 : 0 : : QgsMapRendererQImageJob( settings )
31 : 0 : , mStatus( Idle )
32 : 0 : {
33 : 0 : }
34 : :
35 : 0 : QgsMapRendererParallelJob::~QgsMapRendererParallelJob()
36 : 0 : {
37 : 0 : if ( isActive() )
38 : : {
39 : 0 : cancel();
40 : 0 : }
41 : 0 : }
42 : :
43 : 0 : void QgsMapRendererParallelJob::start()
44 : : {
45 : 0 : if ( isActive() )
46 : 0 : return;
47 : :
48 : 0 : mRenderingStart.start();
49 : :
50 : 0 : mStatus = RenderingLayers;
51 : :
52 : 0 : mLabelingEngineV2.reset();
53 : :
54 : 0 : if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) )
55 : : {
56 : 0 : mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
57 : 0 : mLabelingEngineV2->setMapSettings( mSettings );
58 : 0 : }
59 : :
60 : 0 : bool canUseLabelCache = prepareLabelCache();
61 : 0 : mLayerJobs = prepareJobs( nullptr, mLabelingEngineV2.get() );
62 : 0 : mLabelJob = prepareLabelingJob( nullptr, mLabelingEngineV2.get(), canUseLabelCache );
63 : 0 : mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
64 : :
65 : 0 : QgsDebugMsgLevel( QStringLiteral( "QThreadPool max thread count is %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ), 2 );
66 : :
67 : : // start async job
68 : :
69 : 0 : connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
70 : :
71 : 0 : mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
72 : 0 : mFutureWatcher.setFuture( mFuture );
73 : 0 : }
74 : :
75 : 0 : void QgsMapRendererParallelJob::cancel()
76 : : {
77 : 0 : if ( !isActive() )
78 : 0 : return;
79 : :
80 : 0 : QgsDebugMsgLevel( QStringLiteral( "PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
81 : :
82 : 0 : mLabelJob.context.setRenderingStopped( true );
83 : 0 : for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
84 : : {
85 : 0 : it->context.setRenderingStopped( true );
86 : 0 : if ( it->renderer && it->renderer->feedback() )
87 : 0 : it->renderer->feedback()->cancel();
88 : 0 : }
89 : :
90 : 0 : if ( mStatus == RenderingLayers )
91 : : {
92 : 0 : disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
93 : :
94 : 0 : mFutureWatcher.waitForFinished();
95 : :
96 : 0 : renderLayersFinished();
97 : 0 : }
98 : :
99 : 0 : if ( mStatus == RenderingLabels )
100 : : {
101 : 0 : disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
102 : :
103 : 0 : mLabelingFutureWatcher.waitForFinished();
104 : :
105 : 0 : renderingFinished();
106 : 0 : }
107 : :
108 : 0 : if ( mStatus == RenderingSecondPass )
109 : : {
110 : 0 : disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
111 : :
112 : 0 : mSecondPassFutureWatcher.waitForFinished();
113 : :
114 : 0 : renderLayersSecondPassFinished();
115 : 0 : }
116 : :
117 : : Q_ASSERT( mStatus == Idle );
118 : 0 : }
119 : :
120 : 0 : void QgsMapRendererParallelJob::cancelWithoutBlocking()
121 : : {
122 : 0 : if ( !isActive() )
123 : 0 : return;
124 : :
125 : 0 : QgsDebugMsgLevel( QStringLiteral( "PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
126 : :
127 : 0 : mLabelJob.context.setRenderingStopped( true );
128 : 0 : for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
129 : : {
130 : 0 : it->context.setRenderingStopped( true );
131 : 0 : if ( it->renderer && it->renderer->feedback() )
132 : 0 : it->renderer->feedback()->cancel();
133 : 0 : }
134 : :
135 : 0 : if ( mStatus == RenderingLayers )
136 : : {
137 : 0 : disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
138 : 0 : connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
139 : 0 : }
140 : 0 : }
141 : :
142 : 0 : void QgsMapRendererParallelJob::waitForFinished()
143 : : {
144 : 0 : if ( !isActive() )
145 : 0 : return;
146 : :
147 : 0 : if ( mStatus == RenderingLayers )
148 : : {
149 : 0 : disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
150 : :
151 : 0 : QElapsedTimer t;
152 : 0 : t.start();
153 : :
154 : 0 : mFutureWatcher.waitForFinished();
155 : :
156 : 0 : QgsDebugMsgLevel( QStringLiteral( "waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
157 : :
158 : 0 : renderLayersFinished();
159 : 0 : }
160 : :
161 : 0 : if ( mStatus == RenderingLabels )
162 : : {
163 : 0 : disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
164 : :
165 : 0 : QElapsedTimer t;
166 : 0 : t.start();
167 : :
168 : 0 : mLabelingFutureWatcher.waitForFinished();
169 : :
170 : 0 : QgsDebugMsgLevel( QStringLiteral( "waitForFinished (2): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
171 : :
172 : 0 : renderingFinished();
173 : 0 : }
174 : :
175 : 0 : if ( mStatus == RenderingSecondPass )
176 : : {
177 : 0 : disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
178 : :
179 : 0 : QElapsedTimer t;
180 : 0 : t.start();
181 : :
182 : 0 : mSecondPassFutureWatcher.waitForFinished();
183 : :
184 : 0 : QgsDebugMsg( QStringLiteral( "waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ) );
185 : :
186 : 0 : renderLayersSecondPassFinished();
187 : 0 : }
188 : :
189 : : Q_ASSERT( mStatus == Idle );
190 : 0 : }
191 : :
192 : 0 : bool QgsMapRendererParallelJob::isActive() const
193 : : {
194 : 0 : return mStatus != Idle;
195 : : }
196 : :
197 : 0 : bool QgsMapRendererParallelJob::usedCachedLabels() const
198 : : {
199 : 0 : return mLabelJob.cached;
200 : : }
201 : :
202 : 0 : QgsLabelingResults *QgsMapRendererParallelJob::takeLabelingResults()
203 : : {
204 : 0 : if ( mLabelingEngineV2 )
205 : 0 : return mLabelingEngineV2->takeResults();
206 : : else
207 : 0 : return nullptr;
208 : 0 : }
209 : :
210 : 0 : QImage QgsMapRendererParallelJob::renderedImage()
211 : : {
212 : : // if status == Idle we are either waiting for the render to start, OR have finished the render completely.
213 : : // We can differentiate between those states by checking whether mFinalImage is null -- at the "waiting for
214 : : // render to start" state mFinalImage has not yet been created.
215 : 0 : const bool jobIsComplete = mStatus == Idle && !mFinalImage.isNull();
216 : :
217 : 0 : if ( !jobIsComplete )
218 : 0 : return composeImage( mSettings, mLayerJobs, mLabelJob, mCache );
219 : : else
220 : 0 : return mFinalImage; // when rendering labels or idle
221 : 0 : }
222 : :
223 : 0 : void QgsMapRendererParallelJob::renderLayersFinished()
224 : : {
225 : : Q_ASSERT( mStatus == RenderingLayers );
226 : :
227 : 0 : LayerRenderJobs::const_iterator it = mLayerJobs.constBegin();
228 : 0 : for ( ; it != mLayerJobs.constEnd(); ++it )
229 : : {
230 : 0 : if ( !it->errors.isEmpty() )
231 : : {
232 : 0 : mErrors.append( Error( it->layer->id(), it->errors.join( ',' ) ) );
233 : 0 : }
234 : 0 : }
235 : :
236 : : // compose final image for labeling
237 : 0 : if ( mSecondPassLayerJobs.isEmpty() )
238 : : {
239 : 0 : mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob, mCache );
240 : 0 : }
241 : :
242 : 0 : QgsDebugMsgLevel( QStringLiteral( "PARALLEL layers finished" ), 2 );
243 : :
244 : 0 : if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
245 : : {
246 : 0 : mStatus = RenderingLabels;
247 : :
248 : 0 : connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
249 : :
250 : : // now start rendering of labeling!
251 : 0 : mLabelingFuture = QtConcurrent::run( renderLabelsStatic, this );
252 : 0 : mLabelingFutureWatcher.setFuture( mLabelingFuture );
253 : 0 : emit renderingLayersFinished();
254 : 0 : }
255 : : else
256 : : {
257 : 0 : renderingFinished();
258 : : }
259 : 0 : }
260 : :
261 : : #define DEBUG_RENDERING 0
262 : :
263 : 0 : void QgsMapRendererParallelJob::renderingFinished()
264 : : {
265 : : #if DEBUG_RENDERING
266 : : int i = 0;
267 : : for ( LayerRenderJob &job : mLayerJobs )
268 : : {
269 : : if ( job.img )
270 : : {
271 : : job.img->save( QString( "/tmp/first_pass_%1.png" ).arg( i ) );
272 : : }
273 : : if ( job.maskPass.image )
274 : : {
275 : : job.maskPass.image->save( QString( "/tmp/first_pass_%1_mask.png" ).arg( i ) );
276 : : }
277 : : i++;
278 : : }
279 : : if ( mLabelJob.img )
280 : : {
281 : : mLabelJob.img->save( QString( "/tmp/labels.png" ) );
282 : : }
283 : : if ( mLabelJob.maskImage )
284 : : {
285 : : mLabelJob.maskImage->save( QString( "/tmp/labels_mask.png" ) );
286 : : }
287 : : #endif
288 : 0 : if ( ! mSecondPassLayerJobs.isEmpty() )
289 : : {
290 : 0 : mStatus = RenderingSecondPass;
291 : : // We have a second pass to do.
292 : 0 : mSecondPassFuture = QtConcurrent::map( mSecondPassLayerJobs, renderLayerStatic );
293 : 0 : mSecondPassFutureWatcher.setFuture( mSecondPassFuture );
294 : 0 : connect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
295 : 0 : }
296 : : else
297 : : {
298 : 0 : QgsDebugMsgLevel( QStringLiteral( "PARALLEL finished" ), 2 );
299 : :
300 : 0 : logRenderingTime( mLayerJobs, mSecondPassLayerJobs, mLabelJob );
301 : :
302 : 0 : cleanupJobs( mLayerJobs );
303 : :
304 : 0 : cleanupLabelJob( mLabelJob );
305 : :
306 : 0 : mStatus = Idle;
307 : :
308 : 0 : mRenderingTime = mRenderingStart.elapsed();
309 : :
310 : 0 : emit finished();
311 : : }
312 : 0 : }
313 : :
314 : 0 : void QgsMapRendererParallelJob::renderLayersSecondPassFinished()
315 : : {
316 : 0 : QgsDebugMsgLevel( QStringLiteral( "PARALLEL finished" ), 2 );
317 : :
318 : : // compose second pass images into first pass images
319 : 0 : composeSecondPass( mSecondPassLayerJobs, mLabelJob );
320 : :
321 : : // compose final image
322 : 0 : mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
323 : :
324 : 0 : logRenderingTime( mLayerJobs, mSecondPassLayerJobs, mLabelJob );
325 : :
326 : 0 : cleanupJobs( mLayerJobs );
327 : :
328 : 0 : cleanupSecondPassJobs( mSecondPassLayerJobs );
329 : :
330 : 0 : cleanupLabelJob( mLabelJob );
331 : :
332 : 0 : mStatus = Idle;
333 : :
334 : 0 : mRenderingTime = mRenderingStart.elapsed();
335 : :
336 : 0 : emit finished();
337 : 0 : }
338 : :
339 : : /*
340 : : * See section "Smarter Map Redraws"
341 : : * in https://github.com/qgis/QGIS-Enhancement-Proposals/issues/181
342 : : */
343 : : // #define SIMULATE_SLOW_RENDERER
344 : :
345 : 0 : void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
346 : : {
347 : 0 : if ( job.context.renderingStopped() )
348 : 0 : return;
349 : :
350 : 0 : if ( job.cached )
351 : 0 : return;
352 : :
353 : 0 : if ( job.img )
354 : : {
355 : 0 : job.img->fill( 0 );
356 : 0 : job.imageInitialized = true;
357 : 0 : }
358 : :
359 : 0 : QElapsedTimer t;
360 : 0 : t.start();
361 : 0 : QgsDebugMsgLevel( QStringLiteral( "job %1 start (layer %2)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.layerId ), 2 );
362 : : try
363 : : {
364 : : #ifdef SIMULATE_SLOW_RENDERER
365 : : QThread::sleep( 1 );
366 : : #endif
367 : 0 : job.completed = job.renderer->render();
368 : 0 : }
369 : : catch ( QgsException &e )
370 : : {
371 : 0 : Q_UNUSED( e )
372 : 0 : QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
373 : 0 : }
374 : : catch ( std::exception &e )
375 : : {
376 : 0 : Q_UNUSED( e )
377 : 0 : QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
378 : 0 : }
379 : : catch ( ... )
380 : : {
381 : 0 : QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
382 : 0 : }
383 : :
384 : 0 : job.errors = job.renderer->errors();
385 : 0 : job.renderingTime += t.elapsed();
386 : 0 : QgsDebugMsgLevel( QStringLiteral( "job %1 end [%2 ms] (layer %3)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layerId ), 2 );
387 : 0 : }
388 : :
389 : :
390 : 0 : void QgsMapRendererParallelJob::renderLabelsStatic( QgsMapRendererParallelJob *self )
391 : : {
392 : 0 : LabelRenderJob &job = self->mLabelJob;
393 : :
394 : 0 : if ( !job.cached )
395 : : {
396 : 0 : QElapsedTimer labelTime;
397 : 0 : labelTime.start();
398 : :
399 : 0 : QPainter painter;
400 : 0 : if ( job.img )
401 : : {
402 : 0 : job.img->fill( 0 );
403 : 0 : painter.begin( job.img );
404 : 0 : }
405 : : else
406 : : {
407 : 0 : painter.begin( &self->mFinalImage );
408 : : }
409 : :
410 : : // draw the labels!
411 : : try
412 : : {
413 : 0 : drawLabeling( job.context, self->mLabelingEngineV2.get(), &painter );
414 : 0 : }
415 : : catch ( QgsException &e )
416 : : {
417 : 0 : Q_UNUSED( e )
418 : 0 : QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
419 : 0 : }
420 : : catch ( std::exception &e )
421 : : {
422 : 0 : Q_UNUSED( e )
423 : 0 : QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
424 : 0 : }
425 : : catch ( ... )
426 : : {
427 : 0 : QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
428 : 0 : }
429 : :
430 : 0 : painter.end();
431 : :
432 : 0 : job.renderingTime = labelTime.elapsed();
433 : 0 : job.complete = true;
434 : 0 : job.participatingLayers = _qgis_listRawToQPointer( self->mLabelingEngineV2->participatingLayers() );
435 : 0 : if ( job.img )
436 : : {
437 : 0 : self->mFinalImage = composeImage( self->mSettings, self->mLayerJobs, self->mLabelJob );
438 : 0 : }
439 : 0 : }
440 : 0 : }
441 : :
|