QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
qgsmaprendererparalleljob.cpp
Go to the documentation of this file.
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 
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 
30  : QgsMapRendererQImageJob( settings )
31  , mStatus( Idle )
32 {
33 }
34 
36 {
37  if ( isActive() )
38  {
39  cancel();
40  }
41 }
42 
44 {
45  if ( isActive() )
46  return;
47 
48  mRenderingStart.start();
49 
50  mStatus = RenderingLayers;
51 
52  mLabelingEngineV2.reset();
53 
55  {
56  mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
57  mLabelingEngineV2->setMapSettings( mSettings );
58  }
59 
60  bool canUseLabelCache = prepareLabelCache();
61  mLayerJobs = prepareJobs( nullptr, mLabelingEngineV2.get() );
62  mLabelJob = prepareLabelingJob( nullptr, mLabelingEngineV2.get(), canUseLabelCache );
63  mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
64 
65  QgsDebugMsgLevel( QStringLiteral( "QThreadPool max thread count is %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ), 2 );
66 
67  // start async job
68 
69  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
70 
71  mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
72  mFutureWatcher.setFuture( mFuture );
73 }
74 
76 {
77  if ( !isActive() )
78  return;
79 
80  QgsDebugMsgLevel( QStringLiteral( "PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
81 
82  mLabelJob.context.setRenderingStopped( true );
83  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
84  {
85  it->context.setRenderingStopped( true );
86  if ( it->renderer && it->renderer->feedback() )
87  it->renderer->feedback()->cancel();
88  }
89 
90  if ( mStatus == RenderingLayers )
91  {
92  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
93 
94  mFutureWatcher.waitForFinished();
95 
96  renderLayersFinished();
97  }
98 
99  if ( mStatus == RenderingLabels )
100  {
101  disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
102 
103  mLabelingFutureWatcher.waitForFinished();
104 
105  renderingFinished();
106  }
107 
108  if ( mStatus == RenderingSecondPass )
109  {
110  disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
111 
112  mSecondPassFutureWatcher.waitForFinished();
113 
114  renderLayersSecondPassFinished();
115  }
116 
117  Q_ASSERT( mStatus == Idle );
118 }
119 
121 {
122  if ( !isActive() )
123  return;
124 
125  QgsDebugMsgLevel( QStringLiteral( "PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
126 
127  mLabelJob.context.setRenderingStopped( true );
128  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
129  {
130  it->context.setRenderingStopped( true );
131  if ( it->renderer && it->renderer->feedback() )
132  it->renderer->feedback()->cancel();
133  }
134 
135  if ( mStatus == RenderingLayers )
136  {
137  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
138  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
139  }
140 }
141 
143 {
144  if ( !isActive() )
145  return;
146 
147  if ( mStatus == RenderingLayers )
148  {
149  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
150 
151  QElapsedTimer t;
152  t.start();
153 
154  mFutureWatcher.waitForFinished();
155 
156  QgsDebugMsgLevel( QStringLiteral( "waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
157 
158  renderLayersFinished();
159  }
160 
161  if ( mStatus == RenderingLabels )
162  {
163  disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
164 
165  QElapsedTimer t;
166  t.start();
167 
168  mLabelingFutureWatcher.waitForFinished();
169 
170  QgsDebugMsgLevel( QStringLiteral( "waitForFinished (2): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
171 
172  renderingFinished();
173  }
174 
175  if ( mStatus == RenderingSecondPass )
176  {
177  disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
178 
179  QElapsedTimer t;
180  t.start();
181 
182  mSecondPassFutureWatcher.waitForFinished();
183 
184  QgsDebugMsg( QStringLiteral( "waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ) );
185 
186  renderLayersSecondPassFinished();
187  }
188 
189  Q_ASSERT( mStatus == Idle );
190 }
191 
193 {
194  return mStatus != Idle;
195 }
196 
198 {
199  return mLabelJob.cached;
200 }
201 
203 {
204  if ( mLabelingEngineV2 )
205  return mLabelingEngineV2->takeResults();
206  else
207  return nullptr;
208 }
209 
211 {
212  if ( mStatus == RenderingLayers )
213  return composeImage( mSettings, mLayerJobs, mLabelJob );
214  else
215  return mFinalImage; // when rendering labels or idle
216 }
217 
218 void QgsMapRendererParallelJob::renderLayersFinished()
219 {
220  Q_ASSERT( mStatus == RenderingLayers );
221 
222  LayerRenderJobs::const_iterator it = mLayerJobs.constBegin();
223  for ( ; it != mLayerJobs.constEnd(); ++it )
224  {
225  if ( !it->errors.isEmpty() )
226  {
227  mErrors.append( Error( it->layer->id(), it->errors.join( ',' ) ) );
228  }
229  }
230 
231  // compose final image for labeling
232  if ( mSecondPassLayerJobs.isEmpty() )
233  {
234  mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
235  }
236 
237  QgsDebugMsgLevel( QStringLiteral( "PARALLEL layers finished" ), 2 );
238 
239  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
240  {
241  mStatus = RenderingLabels;
242 
243  connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
244 
245  // now start rendering of labeling!
246  mLabelingFuture = QtConcurrent::run( renderLabelsStatic, this );
247  mLabelingFutureWatcher.setFuture( mLabelingFuture );
249  }
250  else
251  {
252  renderingFinished();
253  }
254 }
255 
256 #define DEBUG_RENDERING 0
257 
258 void QgsMapRendererParallelJob::renderingFinished()
259 {
260 #if DEBUG_RENDERING
261  int i = 0;
262  for ( LayerRenderJob &job : mLayerJobs )
263  {
264  if ( job.img )
265  {
266  job.img->save( QString( "/tmp/first_pass_%1.png" ).arg( i ) );
267  }
268  if ( job.maskPass.image )
269  {
270  job.maskPass.image->save( QString( "/tmp/first_pass_%1_mask.png" ).arg( i ) );
271  }
272  i++;
273  }
274  if ( mLabelJob.img )
275  {
276  mLabelJob.img->save( QString( "/tmp/labels.png" ) );
277  }
278  if ( mLabelJob.maskImage )
279  {
280  mLabelJob.maskImage->save( QString( "/tmp/labels_mask.png" ) );
281  }
282 #endif
283  if ( ! mSecondPassLayerJobs.isEmpty() )
284  {
285  mStatus = RenderingSecondPass;
286  // We have a second pass to do.
287  mSecondPassFuture = QtConcurrent::map( mSecondPassLayerJobs, renderLayerStatic );
288  mSecondPassFutureWatcher.setFuture( mSecondPassFuture );
289  connect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
290  }
291  else
292  {
293  QgsDebugMsgLevel( QStringLiteral( "PARALLEL finished" ), 2 );
294 
295  logRenderingTime( mLayerJobs, mSecondPassLayerJobs, mLabelJob );
296 
297  cleanupJobs( mLayerJobs );
298 
299  cleanupLabelJob( mLabelJob );
300 
301  mStatus = Idle;
302 
303  mRenderingTime = mRenderingStart.elapsed();
304 
305  emit finished();
306  }
307 }
308 
309 void QgsMapRendererParallelJob::renderLayersSecondPassFinished()
310 {
311  QgsDebugMsgLevel( QStringLiteral( "PARALLEL finished" ), 2 );
312 
313  // compose second pass images into first pass images
314  composeSecondPass( mSecondPassLayerJobs, mLabelJob );
315 
316  // compose final image
317  mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
318 
319  logRenderingTime( mLayerJobs, mSecondPassLayerJobs, mLabelJob );
320 
321  cleanupJobs( mLayerJobs );
322 
323  cleanupSecondPassJobs( mSecondPassLayerJobs );
324 
325  cleanupLabelJob( mLabelJob );
326 
327  mStatus = Idle;
328 
329  mRenderingTime = mRenderingStart.elapsed();
330 
331  emit finished();
332 }
333 
334 void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
335 {
336  if ( job.context.renderingStopped() )
337  return;
338 
339  if ( job.cached )
340  return;
341 
342  if ( job.img )
343  {
344  job.img->fill( 0 );
345  job.imageInitialized = true;
346  }
347 
348  QElapsedTimer t;
349  t.start();
350  QgsDebugMsgLevel( QStringLiteral( "job %1 start (layer %2)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.layerId ), 2 );
351  try
352  {
353  job.renderer->render();
354  }
355  catch ( QgsException &e )
356  {
357  Q_UNUSED( e )
358  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
359  }
360  catch ( std::exception &e )
361  {
362  Q_UNUSED( e )
363  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
364  }
365  catch ( ... )
366  {
367  QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
368  }
369 
370  job.errors = job.renderer->errors();
371  job.renderingTime += t.elapsed();
372  QgsDebugMsgLevel( QStringLiteral( "job %1 end [%2 ms] (layer %3)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layerId ), 2 );
373 }
374 
375 
376 void QgsMapRendererParallelJob::renderLabelsStatic( QgsMapRendererParallelJob *self )
377 {
378  LabelRenderJob &job = self->mLabelJob;
379 
380  if ( !job.cached )
381  {
382  QElapsedTimer labelTime;
383  labelTime.start();
384 
385  QPainter painter;
386  if ( job.img )
387  {
388  job.img->fill( 0 );
389  painter.begin( job.img );
390  }
391  else
392  {
393  painter.begin( &self->mFinalImage );
394  }
395 
396  // draw the labels!
397  try
398  {
399  drawLabeling( job.context, self->mLabelingEngineV2.get(), &painter );
400  }
401  catch ( QgsException &e )
402  {
403  Q_UNUSED( e )
404  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
405  }
406  catch ( std::exception &e )
407  {
408  Q_UNUSED( e )
409  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
410  }
411  catch ( ... )
412  {
413  QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
414  }
415 
416  painter.end();
417 
418  job.renderingTime = labelTime.elapsed();
419  job.complete = true;
420  job.participatingLayers = _qgis_listRawToQPointer( self->mLabelingEngineV2->participatingLayers() );
421  if ( job.img )
422  {
423  self->mFinalImage = composeImage( self->mSettings, self->mLayerJobs, self->mLabelJob );
424  }
425  }
426 }
427 
QgsException
Defines a QGIS exception class.
Definition: qgsexception.h:35
QgsMapRendererJob::logRenderingTime
void logRenderingTime(const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob)
QgsMapRendererParallelJob::isActive
bool isActive() const override
Tell whether the rendering job is currently running in background.
Definition: qgsmaprendererparalleljob.cpp:192
QgsMapRendererJob::cleanupJobs
void cleanupJobs(LayerRenderJobs &jobs)
QgsMapRendererJob::mErrors
Errors mErrors
Definition: qgsmaprendererjob.h:317
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsMapRendererJob::renderingLayersFinished
void renderingLayersFinished()
Emitted when the layers are rendered.
QgsLabelingResults
Class that stores computed placement from labeling engine.
Definition: qgspallabeling.h:1224
QgsMapRendererJob::mRenderingStart
QElapsedTimer mRenderingStart
Definition: qgsmaprendererjob.h:316
QgsMapRendererJob::prepareLabelCache
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
QgsMapRendererJob::prepareJobs
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
QgsDefaultLabelingEngine
Default QgsLabelingEngine implementation, which completes the whole labeling operation (including lab...
Definition: qgslabelingengine.h:337
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsMapRendererJob::cleanupSecondPassJobs
void cleanupSecondPassJobs(LayerRenderJobs &jobs)
QgsMapRendererJob::prepareLabelingJob
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
QgsMapSettings::DrawLabeling
@ DrawLabeling
Enable drawing of labels on top of the map.
Definition: qgsmapsettings.h:307
QgsMapRendererParallelJob
Job implementation that renders all layers in parallel.
Definition: qgsmaprendererparalleljob.h:33
QgsMapRendererJob::mRenderingTime
int mRenderingTime
Definition: qgsmaprendererjob.h:321
QgsMapRendererParallelJob::start
void start() override
Start the rendering job and immediately return.
Definition: qgsmaprendererparalleljob.cpp:43
QgsMapRendererParallelJob::~QgsMapRendererParallelJob
~QgsMapRendererParallelJob() override
Definition: qgsmaprendererparalleljob.cpp:35
QgsException::what
QString what() const
Definition: qgsexception.h:48
qgsmaplayer.h
QgsMapRendererJob::composeImage
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
qgsmaplayerrenderer.h
QgsMapRendererJob::cleanupLabelJob
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
QgsMapRendererParallelJob::takeLabelingResults
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results).
Definition: qgsmaprendererparalleljob.cpp:202
QgsMapRendererParallelJob::waitForFinished
void waitForFinished() override
Block until the job has finished.
Definition: qgsmaprendererparalleljob.cpp:142
QgsMapRendererParallelJob::cancel
void cancel() override
Stop the rendering job - does not return until the job has terminated.
Definition: qgsmaprendererparalleljob.cpp:75
QgsMapRendererQImageJob
Intermediate base class adding functionality that allows client to query the rendered image.
Definition: qgsmaprendererjob.h:440
QgsMapSettings::testFlag
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
Definition: qgsmapsettings.cpp:355
QgsMapRendererJob::mSettings
QgsMapSettings mSettings
Definition: qgsmaprendererjob.h:315
QgsMapRendererJob::composeSecondPass
static void composeSecondPass(LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
qgsmaprendererparalleljob.h
qgsmaplayerlistutils.h
qgslogger.h
QgsMapRendererJob::prepareSecondPassJobs
LayerRenderJobs prepareSecondPassJobs(LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
QgsMapSettings
The QgsMapSettings class contains configuration for rendering of the map.
Definition: qgsmapsettings.h:88
qgsfeedback.h
QgsMapRendererParallelJob::renderedImage
QImage renderedImage() override
Gets a preview/resulting image.
Definition: qgsmaprendererparalleljob.cpp:210
QgsMapRendererParallelJob::cancelWithoutBlocking
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
Definition: qgsmaprendererparalleljob.cpp:120
QgsMapRendererParallelJob::usedCachedLabels
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
Definition: qgsmaprendererparalleljob.cpp:197
qgsproject.h
QgsMapRendererParallelJob::QgsMapRendererParallelJob
QgsMapRendererParallelJob(const QgsMapSettings &settings)
Definition: qgsmaprendererparalleljob.cpp:29
qgslabelingengine.h
QgsMapRendererJob::finished
void finished()
emitted when asynchronous rendering is finished (or canceled).
QgsMapRendererJob::drawLabeling
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)