30#include <QtConcurrentMap>
31#include <QtConcurrentRun>
33#include "moc_qgsmaprendererparalleljob.cpp"
35using namespace Qt::StringLiterals;
42 QgsLogger::warning( u
"Vector rendering in parallel job is not supported, so Qgis::MapSettingsFlag::ForceVectorOutput option will be ignored!"_s );
43 mSettings.setFlag( Qgis::MapSettingsFlag::ForceVectorOutput, false );
55void QgsMapRendererParallelJob::startPrivate()
62 mStatus = RenderingLayers;
64 mLabelingEngineV2.reset();
68 mLabelingEngineV2 = std::make_unique<QgsDefaultLabelingEngine>( );
69 mLabelingEngineV2->setMapSettings(
mSettings );
73 mLayerJobs =
prepareJobs(
nullptr, mLabelingEngineV2.get() );
77 QgsDebugMsgLevel( u
"QThreadPool max thread count is %1"_s.arg( QThreadPool::globalInstance()->maxThreadCount() ), 2 );
81 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
83 mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
84 mFutureWatcher.setFuture( mFuture );
94 mLabelJob.context.setRenderingStopped(
true );
95 for ( LayerRenderJob &job : mLayerJobs )
97 job.context()->setRenderingStopped(
true );
98 if ( job.renderer && job.renderer->feedback() )
99 job.renderer->feedback()->cancel();
102 if ( mStatus == RenderingLayers )
104 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
106 mFutureWatcher.waitForFinished();
108 renderLayersFinished();
111 if ( mStatus == RenderingLabels )
113 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
115 mLabelingFutureWatcher.waitForFinished();
120 if ( mStatus == RenderingSecondPass )
122 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
124 mSecondPassFutureWatcher.waitForFinished();
126 renderLayersSecondPassFinished();
129 Q_ASSERT( mStatus == Idle );
139 mLabelJob.context.setRenderingStopped(
true );
140 for ( LayerRenderJob &job : mLayerJobs )
142 job.context()->setRenderingStopped(
true );
143 if ( job.renderer && job.renderer->feedback() )
144 job.renderer->feedback()->cancel();
147 if ( mStatus == RenderingLayers )
149 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
150 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
159 if ( mStatus == RenderingLayers )
161 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
166 mFutureWatcher.waitForFinished();
168 QgsDebugMsgLevel( u
"waitForFinished (1): %1 ms"_s.arg( t.elapsed() / 1000.0 ), 2 );
170 renderLayersFinished();
173 if ( mStatus == RenderingLabels )
175 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
180 mLabelingFutureWatcher.waitForFinished();
182 QgsDebugMsgLevel( u
"waitForFinished (2): %1 ms"_s.arg( t.elapsed() / 1000.0 ), 2 );
187 if ( mStatus == RenderingSecondPass )
189 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
194 mSecondPassFutureWatcher.waitForFinished();
196 QgsDebugMsgLevel( u
"waitForFinished (1): %1 ms"_s.arg( t.elapsed() / 1000.0 ), 2 );
198 renderLayersSecondPassFinished();
201 Q_ASSERT( mStatus == Idle );
206 return mStatus != Idle;
211 return mLabelJob.cached;
216 if ( mLabelingEngineV2 )
217 return mLabelingEngineV2->takeResults();
227 const bool jobIsComplete = mStatus == Idle && !mFinalImage.isNull();
229 if ( !jobIsComplete )
235void QgsMapRendererParallelJob::renderLayersFinished()
237 Q_ASSERT( mStatus == RenderingLayers );
239 for (
const LayerRenderJob &job : mLayerJobs )
241 if ( !job.errors.isEmpty() )
243 mErrors.append( Error( job.layerId, job.errors.join(
',' ) ) );
248 if ( mSecondPassLayerJobs.empty() )
257 mStatus = RenderingLabels;
259 connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
262 mLabelingFuture = QtConcurrent::run( renderLabelsStatic,
this );
263 mLabelingFutureWatcher.setFuture( mLabelingFuture );
272#define DEBUG_RENDERING 0
274void QgsMapRendererParallelJob::renderingFinished()
278 for ( LayerRenderJob &job : mLayerJobs )
282 job.img->save( QString(
"/tmp/first_pass_%1.png" ).arg( i ) );
284 if ( job.maskPass.image )
286 job.maskPass.image->save( QString(
"/tmp/first_pass_%1_mask.png" ).arg( i ) );
292 mLabelJob.img->save( QString(
"/tmp/labels.png" ) );
294 if ( mLabelJob.maskImage )
296 mLabelJob.maskImage->save( QString(
"/tmp/labels_mask.png" ) );
299 if ( ! mSecondPassLayerJobs.empty() )
303 mStatus = RenderingSecondPass;
305 connect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
306 mSecondPassFuture = QtConcurrent::map( mSecondPassLayerJobs, renderLayerStatic );
307 mSecondPassFutureWatcher.setFuture( mSecondPassFuture );
327void QgsMapRendererParallelJob::renderLayersSecondPassFinished()
358void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
360 if ( job.context()->renderingStopped() )
366 if ( job.previewRenderImage && !job.previewRenderImageInitialized )
368 job.previewRenderImage->fill( 0 );
369 job.previewRenderImageInitialized =
true;
375 job.imageInitialized =
true;
380 QgsDebugMsgLevel( u
"job %1 start (layer %2)"_s.arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.layerId ), 2 );
383#ifdef SIMULATE_SLOW_RENDERER
386 job.completed = job.renderer->render();
388 catch ( QgsException &e )
393 catch ( std::exception &e )
396 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
403 job.errors = job.renderer->errors();
404 job.renderingTime += t.elapsed();
405 QgsDebugMsgLevel( u
"job %1 end [%2 ms] (layer %3)"_s.arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layerId ), 2 );
411 LabelRenderJob &job = self->mLabelJob;
415 QElapsedTimer labelTime;
422 painter.begin( job.img );
424 else if ( job.picture )
426 painter.begin( job.picture.get() );
430 painter.begin( &self->mFinalImage );
436 QgsScopedThreadName threadName( u
"labeling"_s );
437 drawLabeling( job.context, self->mLabelingEngineV2.get(), &painter );
439 catch ( QgsException &e )
444 catch ( std::exception &e )
447 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
456 job.renderingTime = labelTime.elapsed();
@ ForceVectorOutput
Vector graphics should not be cached and drawn as raster images.
@ DrawLabeling
Enable drawing of labels on top of the map.
Stores computed placement from labeling engine.
void logRenderingTime(const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob)
QList< QPointer< QgsMapLayer > > participatingLabelLayers(QgsLabelingEngine *engine)
Returns a list of the layers participating in the map labeling.
static QImage composeImage(const QgsMapSettings &settings, const std::vector< LayerRenderJob > &jobs, const LabelRenderJob &labelJob, const QgsMapRendererCache *cache=nullptr)
void cleanupSecondPassJobs(std::vector< LayerRenderJob > &jobs)
void initSecondPassJobs(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob) const
Initialize secondPassJobs according to what have been rendered (mask clipping path e....
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
std::vector< LayerRenderJob > prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
void renderingLayersFinished()
Emitted when the layers are rendered.
void cleanupJobs(std::vector< LayerRenderJob > &jobs)
QElapsedTimer mRenderingStart
QgsMapRendererCache * mCache
void finished()
emitted when asynchronous rendering is finished (or canceled).
static void composeSecondPass(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob, bool forceVector=false)
Compose second pass images into first pass images.
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).
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
Job implementation that renders all layers in parallel.
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results).
bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
bool isActive() const override
Tell whether the rendering job is currently running in background.
QgsMapRendererParallelJob(const QgsMapSettings &settings)
void cancelWithoutBlocking() override
Triggers cancellation of the rendering job without blocking.
QImage renderedImage() override
Gets a preview/resulting image.
void cancel() override
Stop the rendering job - does not return until the job has terminated.
~QgsMapRendererParallelJob() override
void waitForFinished() override
Block until the job has finished.
QgsMapRendererQImageJob(const QgsMapSettings &settings)
Contains configuration for rendering maps.
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)