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>(
mSettings );
72 mLayerJobs =
prepareJobs(
nullptr, mLabelingEngineV2.get() );
76 QgsDebugMsgLevel( u
"QThreadPool max thread count is %1"_s.arg( QThreadPool::globalInstance()->maxThreadCount() ), 2 );
80 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
82 mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
83 mFutureWatcher.setFuture( mFuture );
93 mLabelJob.context.setRenderingStopped(
true );
94 for ( LayerRenderJob &job : mLayerJobs )
96 job.context()->setRenderingStopped(
true );
97 if ( job.renderer && job.renderer->feedback() )
98 job.renderer->feedback()->cancel();
101 if ( mStatus == RenderingLayers )
103 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
105 mFutureWatcher.waitForFinished();
107 renderLayersFinished();
110 if ( mStatus == RenderingLabels )
112 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
114 mLabelingFutureWatcher.waitForFinished();
119 if ( mStatus == RenderingSecondPass )
121 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
123 mSecondPassFutureWatcher.waitForFinished();
125 renderLayersSecondPassFinished();
128 Q_ASSERT( mStatus == Idle );
138 mLabelJob.context.setRenderingStopped(
true );
139 for ( LayerRenderJob &job : mLayerJobs )
141 job.context()->setRenderingStopped(
true );
142 if ( job.renderer && job.renderer->feedback() )
143 job.renderer->feedback()->cancel();
146 if ( mStatus == RenderingLayers )
148 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
149 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
158 if ( mStatus == RenderingLayers )
160 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
165 mFutureWatcher.waitForFinished();
167 QgsDebugMsgLevel( u
"waitForFinished (1): %1 ms"_s.arg( t.elapsed() / 1000.0 ), 2 );
169 renderLayersFinished();
172 if ( mStatus == RenderingLabels )
174 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
179 mLabelingFutureWatcher.waitForFinished();
181 QgsDebugMsgLevel( u
"waitForFinished (2): %1 ms"_s.arg( t.elapsed() / 1000.0 ), 2 );
186 if ( mStatus == RenderingSecondPass )
188 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
193 mSecondPassFutureWatcher.waitForFinished();
195 QgsDebugMsgLevel( u
"waitForFinished (1): %1 ms"_s.arg( t.elapsed() / 1000.0 ), 2 );
197 renderLayersSecondPassFinished();
200 Q_ASSERT( mStatus == Idle );
205 return mStatus != Idle;
210 return mLabelJob.cached;
215 if ( mLabelingEngineV2 )
216 return mLabelingEngineV2->takeResults();
226 const bool jobIsComplete = mStatus == Idle && !mFinalImage.isNull();
228 if ( !jobIsComplete )
234void QgsMapRendererParallelJob::renderLayersFinished()
236 Q_ASSERT( mStatus == RenderingLayers );
238 for (
const LayerRenderJob &job : mLayerJobs )
240 if ( !job.errors.isEmpty() )
242 mErrors.append( Error( job.layerId, job.errors.join(
',' ) ) );
247 if ( mSecondPassLayerJobs.empty() )
256 mStatus = RenderingLabels;
258 connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
261 mLabelingFuture = QtConcurrent::run( renderLabelsStatic,
this );
262 mLabelingFutureWatcher.setFuture( mLabelingFuture );
271#define DEBUG_RENDERING 0
273void QgsMapRendererParallelJob::renderingFinished()
277 for ( LayerRenderJob &job : mLayerJobs )
281 job.img->save( QString(
"/tmp/first_pass_%1.png" ).arg( i ) );
283 if ( job.maskPass.image )
285 job.maskPass.image->save( QString(
"/tmp/first_pass_%1_mask.png" ).arg( i ) );
291 mLabelJob.img->save( QString(
"/tmp/labels.png" ) );
293 if ( mLabelJob.maskImage )
295 mLabelJob.maskImage->save( QString(
"/tmp/labels_mask.png" ) );
298 if ( !mSecondPassLayerJobs.empty() )
302 mStatus = RenderingSecondPass;
304 connect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
305 mSecondPassFuture = QtConcurrent::map( mSecondPassLayerJobs, renderLayerStatic );
306 mSecondPassFutureWatcher.setFuture( mSecondPassFuture );
326void QgsMapRendererParallelJob::renderLayersSecondPassFinished()
357void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
359 if ( job.context()->renderingStopped() )
365 if ( job.previewRenderImage && !job.previewRenderImageInitialized )
367 job.previewRenderImage->fill( 0 );
368 job.previewRenderImageInitialized =
true;
374 job.imageInitialized =
true;
379 QgsDebugMsgLevel( u
"job %1 start (layer %2)"_s.arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.layerId ), 2 );
382#ifdef SIMULATE_SLOW_RENDERER
385 job.completed = job.renderer->render();
387 catch ( QgsException &e )
392 catch ( std::exception &e )
395 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
402 job.errors = job.renderer->errors();
403 job.renderingTime += t.elapsed();
404 QgsDebugMsgLevel( u
"job %1 end [%2 ms] (layer %3)"_s.arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layerId ), 2 );
410 LabelRenderJob &job = self->mLabelJob;
414 QElapsedTimer labelTime;
421 painter.begin( job.img );
423 else if ( job.picture )
425 painter.begin( job.picture.get() );
429 painter.begin( &self->mFinalImage );
435 QgsScopedThreadName threadName( u
"labeling"_s );
436 drawLabeling( job.context, self->mLabelingEngineV2.get(), &painter );
438 catch ( QgsException &e )
443 catch ( std::exception &e )
446 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
455 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)