QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 
void finished()
emitted when asynchronous rendering is finished (or canceled).
QElapsedTimer mRenderingStart
void waitForFinished() override
Block until the job has finished.
void cleanupJobs(LayerRenderJobs &jobs)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
void cleanupSecondPassJobs(LayerRenderJobs &jobs)
void logRenderingTime(const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob)
Default QgsLabelingEngine implementation, which completes the whole labeling operation (including lab...
void renderingLayersFinished()
Emitted when the layers are rendered.
void cancel() override
Stop the rendering job - does not return until the job has terminated.
Enable drawing of labels on top of the map.
QString what() const
Definition: qgsexception.h:48
The QgsMapSettings class contains configuration for rendering of the map.
QgsMapRendererParallelJob(const QgsMapSettings &settings)
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
Job implementation that renders all layers in parallel.
QgsMapSettings mSettings
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
LayerRenderJobs prepareSecondPassJobs(LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
Intermediate base class adding functionality that allows client to query the rendered image...
void start() override
Start the rendering job and immediately return.
static void composeSecondPass(LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
Class that stores computed placement from labeling engine.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
Defines a QGIS exception class.
Definition: qgsexception.h:34
bool isActive() const override
Tell whether the rendering job is currently running in background.
QImage renderedImage() override
Gets a preview/resulting image.
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results). ...