28#include <QtConcurrentMap>
29#include <QtConcurrentRun>
31#include "moc_qgsmaprendererparalleljob.cpp"
38 QgsLogger::warning( QStringLiteral(
"Vector rendering in parallel job is not supported, so Qgis::MapSettingsFlag::ForceVectorOutput option will be ignored!" ) );
39 mSettings.setFlag( Qgis::MapSettingsFlag::ForceVectorOutput, false );
51void QgsMapRendererParallelJob::startPrivate()
58 mStatus = RenderingLayers;
60 mLabelingEngineV2.reset();
64 mLabelingEngineV2 = std::make_unique<QgsDefaultLabelingEngine>( );
65 mLabelingEngineV2->setMapSettings(
mSettings );
69 mLayerJobs =
prepareJobs(
nullptr, mLabelingEngineV2.get() );
73 QgsDebugMsgLevel( QStringLiteral(
"QThreadPool max thread count is %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ), 2 );
77 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
79 mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
80 mFutureWatcher.setFuture( mFuture );
88 QgsDebugMsgLevel( QStringLiteral(
"PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
90 mLabelJob.context.setRenderingStopped(
true );
91 for ( LayerRenderJob &job : mLayerJobs )
93 job.context()->setRenderingStopped(
true );
94 if ( job.renderer && job.renderer->feedback() )
95 job.renderer->feedback()->cancel();
98 if ( mStatus == RenderingLayers )
100 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
102 mFutureWatcher.waitForFinished();
104 renderLayersFinished();
107 if ( mStatus == RenderingLabels )
109 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
111 mLabelingFutureWatcher.waitForFinished();
116 if ( mStatus == RenderingSecondPass )
118 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
120 mSecondPassFutureWatcher.waitForFinished();
122 renderLayersSecondPassFinished();
125 Q_ASSERT( mStatus == Idle );
133 QgsDebugMsgLevel( QStringLiteral(
"PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
135 mLabelJob.context.setRenderingStopped(
true );
136 for ( LayerRenderJob &job : mLayerJobs )
138 job.context()->setRenderingStopped(
true );
139 if ( job.renderer && job.renderer->feedback() )
140 job.renderer->feedback()->cancel();
143 if ( mStatus == RenderingLayers )
145 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
146 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
155 if ( mStatus == RenderingLayers )
157 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
162 mFutureWatcher.waitForFinished();
164 QgsDebugMsgLevel( QStringLiteral(
"waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
166 renderLayersFinished();
169 if ( mStatus == RenderingLabels )
171 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
176 mLabelingFutureWatcher.waitForFinished();
178 QgsDebugMsgLevel( QStringLiteral(
"waitForFinished (2): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
183 if ( mStatus == RenderingSecondPass )
185 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
190 mSecondPassFutureWatcher.waitForFinished();
192 QgsDebugMsgLevel( QStringLiteral(
"waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
194 renderLayersSecondPassFinished();
197 Q_ASSERT( mStatus == Idle );
202 return mStatus != Idle;
207 return mLabelJob.cached;
212 if ( mLabelingEngineV2 )
213 return mLabelingEngineV2->takeResults();
223 const bool jobIsComplete = mStatus == Idle && !mFinalImage.isNull();
225 if ( !jobIsComplete )
231void QgsMapRendererParallelJob::renderLayersFinished()
233 Q_ASSERT( mStatus == RenderingLayers );
235 for (
const LayerRenderJob &job : mLayerJobs )
237 if ( !job.errors.isEmpty() )
239 mErrors.append( Error( job.layerId, job.errors.join(
',' ) ) );
244 if ( mSecondPassLayerJobs.empty() )
253 mStatus = RenderingLabels;
255 connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
258 mLabelingFuture = QtConcurrent::run( renderLabelsStatic,
this );
259 mLabelingFutureWatcher.setFuture( mLabelingFuture );
268#define DEBUG_RENDERING 0
270void QgsMapRendererParallelJob::renderingFinished()
274 for ( LayerRenderJob &job : mLayerJobs )
278 job.img->save( QString(
"/tmp/first_pass_%1.png" ).arg( i ) );
280 if ( job.maskPass.image )
282 job.maskPass.image->save( QString(
"/tmp/first_pass_%1_mask.png" ).arg( i ) );
288 mLabelJob.img->save( QString(
"/tmp/labels.png" ) );
290 if ( mLabelJob.maskImage )
292 mLabelJob.maskImage->save( QString(
"/tmp/labels_mask.png" ) );
295 if ( ! mSecondPassLayerJobs.empty() )
299 mStatus = RenderingSecondPass;
301 connect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
302 mSecondPassFuture = QtConcurrent::map( mSecondPassLayerJobs, renderLayerStatic );
303 mSecondPassFutureWatcher.setFuture( mSecondPassFuture );
323void QgsMapRendererParallelJob::renderLayersSecondPassFinished()
354void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
356 if ( job.context()->renderingStopped() )
362 if ( job.previewRenderImage && !job.previewRenderImageInitialized )
364 job.previewRenderImage->fill( 0 );
365 job.previewRenderImageInitialized =
true;
371 job.imageInitialized =
true;
376 QgsDebugMsgLevel( QStringLiteral(
"job %1 start (layer %2)" ).arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.layerId ), 2 );
379#ifdef SIMULATE_SLOW_RENDERER
382 job.completed = job.renderer->render();
384 catch ( QgsException &e )
389 catch ( std::exception &e )
392 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
396 QgsDebugError( QStringLiteral(
"Caught unhandled unknown exception" ) );
399 job.errors = job.renderer->errors();
400 job.renderingTime += t.elapsed();
401 QgsDebugMsgLevel( QStringLiteral(
"job %1 end [%2 ms] (layer %3)" ).arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layerId ), 2 );
407 LabelRenderJob &job = self->mLabelJob;
411 QElapsedTimer labelTime;
418 painter.begin( job.img );
420 else if ( job.picture )
422 painter.begin( job.picture.get() );
426 painter.begin( &self->mFinalImage );
432 drawLabeling( job.context, self->mLabelingEngineV2.get(), &painter );
434 catch ( QgsException &e )
439 catch ( std::exception &e )
442 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
446 QgsDebugError( QStringLiteral(
"Caught unhandled unknown exception" ) );
451 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)