26#include <QtConcurrentMap>
27#include <QtConcurrentRun>
35 QgsLogger::warning( QStringLiteral(
"Vector rendering in parallel job is not supported, so Qgis::MapSettingsFlag::ForceVectorOutput option will be ignored!" ) );
48void QgsMapRendererParallelJob::startPrivate()
55 mStatus = RenderingLayers;
57 mLabelingEngineV2.reset();
62 mLabelingEngineV2->setMapSettings(
mSettings );
66 mLayerJobs =
prepareJobs(
nullptr, mLabelingEngineV2.get() );
70 QgsDebugMsgLevel( QStringLiteral(
"QThreadPool max thread count is %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ), 2 );
74 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
76 mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
77 mFutureWatcher.setFuture( mFuture );
85 QgsDebugMsgLevel( QStringLiteral(
"PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
87 mLabelJob.context.setRenderingStopped(
true );
88 for ( LayerRenderJob &job : mLayerJobs )
90 job.context()->setRenderingStopped(
true );
91 if ( job.renderer && job.renderer->feedback() )
92 job.renderer->feedback()->cancel();
95 if ( mStatus == RenderingLayers )
97 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
99 mFutureWatcher.waitForFinished();
101 renderLayersFinished();
104 if ( mStatus == RenderingLabels )
106 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
108 mLabelingFutureWatcher.waitForFinished();
113 if ( mStatus == RenderingSecondPass )
115 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
117 mSecondPassFutureWatcher.waitForFinished();
119 renderLayersSecondPassFinished();
122 Q_ASSERT( mStatus == Idle );
130 QgsDebugMsgLevel( QStringLiteral(
"PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
132 mLabelJob.context.setRenderingStopped(
true );
133 for ( LayerRenderJob &job : mLayerJobs )
135 job.context()->setRenderingStopped(
true );
136 if ( job.renderer && job.renderer->feedback() )
137 job.renderer->feedback()->cancel();
140 if ( mStatus == RenderingLayers )
142 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
143 connect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
152 if ( mStatus == RenderingLayers )
154 disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersFinished );
159 mFutureWatcher.waitForFinished();
161 QgsDebugMsgLevel( QStringLiteral(
"waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
163 renderLayersFinished();
166 if ( mStatus == RenderingLabels )
168 disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
173 mLabelingFutureWatcher.waitForFinished();
175 QgsDebugMsgLevel( QStringLiteral(
"waitForFinished (2): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
180 if ( mStatus == RenderingSecondPass )
182 disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
187 mSecondPassFutureWatcher.waitForFinished();
189 QgsDebugMsgLevel( QStringLiteral(
"waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
191 renderLayersSecondPassFinished();
194 Q_ASSERT( mStatus == Idle );
199 return mStatus != Idle;
204 return mLabelJob.cached;
209 if ( mLabelingEngineV2 )
210 return mLabelingEngineV2->takeResults();
220 const bool jobIsComplete = mStatus == Idle && !mFinalImage.isNull();
222 if ( !jobIsComplete )
228void QgsMapRendererParallelJob::renderLayersFinished()
230 Q_ASSERT( mStatus == RenderingLayers );
232 for (
const LayerRenderJob &job : mLayerJobs )
234 if ( !job.errors.isEmpty() )
236 mErrors.append( Error( job.layerId, job.errors.join(
',' ) ) );
241 if ( mSecondPassLayerJobs.empty() )
250 mStatus = RenderingLabels;
252 connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderingFinished );
255 mLabelingFuture = QtConcurrent::run( renderLabelsStatic,
this );
256 mLabelingFutureWatcher.setFuture( mLabelingFuture );
265#define DEBUG_RENDERING 0
267void QgsMapRendererParallelJob::renderingFinished()
271 for ( LayerRenderJob &job : mLayerJobs )
275 job.img->save( QString(
"/tmp/first_pass_%1.png" ).arg( i ) );
277 if ( job.maskPass.image )
279 job.maskPass.image->save( QString(
"/tmp/first_pass_%1_mask.png" ).arg( i ) );
285 mLabelJob.img->save( QString(
"/tmp/labels.png" ) );
287 if ( mLabelJob.maskImage )
289 mLabelJob.maskImage->save( QString(
"/tmp/labels_mask.png" ) );
292 if ( ! mSecondPassLayerJobs.empty() )
296 mStatus = RenderingSecondPass;
298 connect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished,
this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
299 mSecondPassFuture = QtConcurrent::map( mSecondPassLayerJobs, renderLayerStatic );
300 mSecondPassFutureWatcher.setFuture( mSecondPassFuture );
320void QgsMapRendererParallelJob::renderLayersSecondPassFinished()
351void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
353 if ( job.context()->renderingStopped() )
359 if ( job.previewRenderImage && !job.previewRenderImageInitialized )
361 job.previewRenderImage->fill( 0 );
362 job.previewRenderImageInitialized =
true;
368 job.imageInitialized =
true;
373 QgsDebugMsgLevel( QStringLiteral(
"job %1 start (layer %2)" ).arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.layerId ), 2 );
376#ifdef SIMULATE_SLOW_RENDERER
379 job.completed = job.renderer->render();
386 catch ( std::exception &e )
389 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
393 QgsDebugError( QStringLiteral(
"Caught unhandled unknown exception" ) );
396 job.errors = job.renderer->errors();
397 job.renderingTime += t.elapsed();
398 QgsDebugMsgLevel( QStringLiteral(
"job %1 end [%2 ms] (layer %3)" ).arg(
reinterpret_cast< quint64
>( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layerId ), 2 );
404 LabelRenderJob &job = self->mLabelJob;
408 QElapsedTimer labelTime;
415 painter.begin( job.img );
419 painter.begin( &self->mFinalImage );
425 drawLabeling( job.context, self->mLabelingEngineV2.get(), &painter );
432 catch ( std::exception &e )
435 QgsDebugError(
"Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
439 QgsDebugError( QStringLiteral(
"Caught unhandled unknown exception" ) );
444 job.renderingTime = labelTime.elapsed();
446 job.participatingLayers = _qgis_listRawToQPointer( self->mLabelingEngineV2->participatingLayers() );
@ ForceVectorOutput
Vector graphics should not be cached and drawn as raster images.
@ DrawLabeling
Enable drawing of labels on top of the map.
Default QgsLabelingEngine implementation, which completes the whole labeling operation (including lab...
Defines a QGIS exception class.
Class that stores computed placement from labeling engine.
static void warning(const QString &msg)
Goes to qWarning.
void logRenderingTime(const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob)
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.
Intermediate base class adding functionality that allows client to query the rendered image.
The QgsMapSettings class contains configuration for rendering of the map.
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)