28#include <QtConcurrentMap>
29#include <QtConcurrentRun>
31#include "moc_qgsmaprendererparalleljob.cpp"
33using namespace Qt::StringLiterals;
40 QgsLogger::warning( u
"Vector rendering in parallel job is not supported, so Qgis::MapSettingsFlag::ForceVectorOutput option will be ignored!"_s );
41 mSettings.setFlag( Qgis::MapSettingsFlag::ForceVectorOutput, false );
53void QgsMapRendererParallelJob::startPrivate()
60 mStatus = RenderingLayers;
62 mLabelingEngineV2.reset();
66 mLabelingEngineV2 = std::make_unique<QgsDefaultLabelingEngine>(
mSettings );
70 mLayerJobs =
prepareJobs(
nullptr, mLabelingEngineV2.get() );
74 QgsDebugMsgLevel( u
"QThreadPool max thread count is %1"_s.arg( QThreadPool::globalInstance()->maxThreadCount() ), 2 );
78 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
80 mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
81 mFutureWatcher.setFuture( mFuture );
91 mLabelJob.context.setRenderingStopped(
true );
92 for ( LayerRenderJob &job : mLayerJobs )
94 job.context()->setRenderingStopped(
true );
95 if ( job.renderer && job.renderer->feedback() )
96 job.renderer->feedback()->cancel();
99 if ( mStatus == RenderingLayers )
101 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
103 mFutureWatcher.waitForFinished();
105 renderLayersFinished();
108 if ( mStatus == RenderingLabels )
110 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
112 mLabelingFutureWatcher.waitForFinished();
117 if ( mStatus == RenderingSecondPass )
119 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
121 mSecondPassFutureWatcher.waitForFinished();
123 renderLayersSecondPassFinished();
126 Q_ASSERT( mStatus == Idle );
136 mLabelJob.context.setRenderingStopped(
true );
137 for ( LayerRenderJob &job : mLayerJobs )
139 job.context()->setRenderingStopped(
true );
140 if ( job.renderer && job.renderer->feedback() )
141 job.renderer->feedback()->cancel();
144 if ( mStatus == RenderingLayers )
146 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
147 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
156 if ( mStatus == RenderingLayers )
158 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
163 mFutureWatcher.waitForFinished();
165 QgsDebugMsgLevel( u
"waitForFinished (1): %1 ms"_s.arg( t.elapsed() / 1000.0 ), 2 );
167 renderLayersFinished();
170 if ( mStatus == RenderingLabels )
172 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
177 mLabelingFutureWatcher.waitForFinished();
179 QgsDebugMsgLevel( u
"waitForFinished (2): %1 ms"_s.arg( t.elapsed() / 1000.0 ), 2 );
184 if ( mStatus == RenderingSecondPass )
186 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
191 mSecondPassFutureWatcher.waitForFinished();
193 QgsDebugMsgLevel( u
"waitForFinished (1): %1 ms"_s.arg( t.elapsed() / 1000.0 ), 2 );
195 renderLayersSecondPassFinished();
198 Q_ASSERT( mStatus == Idle );
203 return mStatus != Idle;
208 return mLabelJob.cached;
213 if ( mLabelingEngineV2 )
214 return mLabelingEngineV2->takeResults();
224 const bool jobIsComplete = mStatus == Idle && !mFinalImage.isNull();
226 if ( !jobIsComplete )
232void QgsMapRendererParallelJob::renderLayersFinished()
234 Q_ASSERT( mStatus == RenderingLayers );
236 for (
const LayerRenderJob &job : mLayerJobs )
238 if ( !job.errors.isEmpty() )
240 mErrors.append( Error( job.layerId, job.errors.join(
',' ) ) );
245 if ( mSecondPassLayerJobs.empty() )
254 mStatus = RenderingLabels;
256 connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
259 mLabelingFuture = QtConcurrent::run( renderLabelsStatic,
this );
260 mLabelingFutureWatcher.setFuture( mLabelingFuture );
269#define DEBUG_RENDERING 0
271void QgsMapRendererParallelJob::renderingFinished()
275 for ( LayerRenderJob &job : mLayerJobs )
279 job.img->save( QString(
"/tmp/first_pass_%1.png" ).arg( i ) );
281 if ( job.maskPass.image )
283 job.maskPass.image->save( QString(
"/tmp/first_pass_%1_mask.png" ).arg( i ) );
289 mLabelJob.img->save( QString(
"/tmp/labels.png" ) );
291 if ( mLabelJob.maskImage )
293 mLabelJob.maskImage->save( QString(
"/tmp/labels_mask.png" ) );
296 if ( !mSecondPassLayerJobs.empty() )
300 mStatus = RenderingSecondPass;
302 connect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
303 mSecondPassFuture = QtConcurrent::map( mSecondPassLayerJobs, renderLayerStatic );
304 mSecondPassFutureWatcher.setFuture( mSecondPassFuture );
324void QgsMapRendererParallelJob::renderLayersSecondPassFinished()
355void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
357 if ( job.context()->renderingStopped() )
363 if ( job.previewRenderImage && !job.previewRenderImageInitialized )
365 job.previewRenderImage->fill( 0 );
366 job.previewRenderImageInitialized =
true;
369 if ( job.destinationImage )
371 job.destinationImage->fill( 0 );
372 job.imageInitialized =
true;
377 QgsDebugMsgLevel( u
"job %1 start (layer %2)"_s.arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.layerId ), 2 );
380#ifdef SIMULATE_SLOW_RENDERER
383 job.completed = job.renderer->render();
385 catch ( QgsException &e )
390 catch ( std::exception &e )
393 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
400 job.errors = job.renderer->errors();
401 job.renderingTime +=
static_cast< int >( t.elapsed() );
402 QgsDebugMsgLevel( u
"job %1 end [%2 ms] (layer %3)"_s.arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layerId ), 2 );
408 LabelRenderJob &job = self->mLabelJob;
412 QElapsedTimer labelTime;
419 painter.begin( job.img.get() );
421 else if ( job.picture )
423 painter.begin( job.picture.get() );
427 painter.begin( &self->mFinalImage );
433 QgsScopedThreadName threadName( u
"labeling"_s );
434 drawLabeling( job.context, self->mLabelingEngineV2.get(), &painter );
436 catch ( QgsException &e )
441 catch ( std::exception &e )
444 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
453 job.renderingTime =
static_cast< int >( 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)
static void composeSecondPass(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
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).
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.
void waitForFinished() final
Block until the job has finished.
void cancelWithoutBlocking() final
Triggers cancellation of the rendering job without blocking.
QImage renderedImage() final
Gets a preview/resulting image.
bool isActive() const final
Tell whether the rendering job is currently running in background.
QgsMapRendererParallelJob(const QgsMapSettings &settings)
QgsLabelingResults * takeLabelingResults() final
Gets pointer to internal labeling engine (in order to get access to the results).
bool usedCachedLabels() const final
Returns true if the render job was able to use a cached labeling solution.
~QgsMapRendererParallelJob() override
void cancel() final
Stop the rendering job - does not return until the job has terminated.
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)