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