Skip to content

Commit

Permalink
Merge pull request #4110 from nyalldawson/label_cache
Browse files Browse the repository at this point in the history
[FEATURE] Cache labeling result to avoid unnecessary redraws when refreshing canvas
  • Loading branch information
nyalldawson committed Feb 7, 2017
2 parents 61523c8 + a08137f commit 0f5b3fd
Show file tree
Hide file tree
Showing 19 changed files with 770 additions and 44 deletions.
1 change: 1 addition & 0 deletions python/core/qgsmaprenderercustompainterjob.sip
Expand Up @@ -21,6 +21,7 @@ class QgsMapRendererCustomPainterJob : QgsMapRendererJob
virtual void cancel();
virtual void waitForFinished();
virtual bool isActive() const;
virtual bool usedCachedLabels() const;
virtual QgsLabelingResults* takeLabelingResults() /Transfer/;

//! @note not available in python bindings
Expand Down
2 changes: 2 additions & 0 deletions python/core/qgsmaprendererjob.sip
Expand Up @@ -24,6 +24,8 @@ class QgsMapRendererJob : QObject
//! Tell whether the rendering job is currently running in background.
virtual bool isActive() const = 0;

virtual bool usedCachedLabels() const = 0;

//! Get pointer to internal labeling engine (in order to get access to the results)
virtual QgsLabelingResults* takeLabelingResults() = 0 /Transfer/;

Expand Down
1 change: 1 addition & 0 deletions python/core/qgsmaprendererparalleljob.sip
Expand Up @@ -20,6 +20,7 @@ class QgsMapRendererParallelJob : QgsMapRendererQImageJob
virtual void cancel();
virtual void waitForFinished();
virtual bool isActive() const;
virtual bool usedCachedLabels() const;

virtual QgsLabelingResults* takeLabelingResults() /Transfer/;

Expand Down
1 change: 1 addition & 0 deletions python/core/qgsmaprenderersequentialjob.sip
Expand Up @@ -21,6 +21,7 @@ class QgsMapRendererSequentialJob : QgsMapRendererQImageJob
virtual void cancel();
virtual void waitForFinished();
virtual bool isActive() const;
virtual bool usedCachedLabels() const;

virtual QgsLabelingResults* takeLabelingResults() /Transfer/;

Expand Down
46 changes: 42 additions & 4 deletions src/core/qgsmaprenderercustompainterjob.cpp
Expand Up @@ -23,6 +23,7 @@
#include "qgspallabeling.h"
#include "qgsvectorlayer.h"
#include "qgsrenderer.h"
#include "qgsmaplayerlistutils.h"

QgsMapRendererCustomPainterJob::QgsMapRendererCustomPainterJob( const QgsMapSettings& settings, QPainter* painter )
: QgsMapRendererJob( settings )
Expand Down Expand Up @@ -82,7 +83,9 @@ void QgsMapRendererCustomPainterJob::start()
mLabelingEngineV2->setMapSettings( mSettings );
}

bool canUseLabelCache = prepareLabelCache();
mLayerJobs = prepareJobs( mPainter, mLabelingEngineV2 );
mLabelJob = prepareLabelingJob( mPainter, mLabelingEngineV2, canUseLabelCache );

QgsDebugMsg( "Rendering prepared in (seconds): " + QString( "%1" ).arg( prepareTime.elapsed() / 1000.0 ) );

Expand Down Expand Up @@ -112,7 +115,7 @@ void QgsMapRendererCustomPainterJob::cancel()
QgsDebugMsg( "QPAINTER canceling" );
disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererCustomPainterJob::futureFinished );

mLabelingRenderContext.setRenderingStopped( true );
mLabelJob.context.setRenderingStopped( true );
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
{
it->context.setRenderingStopped( true );
Expand Down Expand Up @@ -154,6 +157,10 @@ bool QgsMapRendererCustomPainterJob::isActive() const
return mActive;
}

bool QgsMapRendererCustomPainterJob::usedCachedLabels() const
{
return mLabelJob.cached;
}

QgsLabelingResults* QgsMapRendererCustomPainterJob::takeLabelingResults()
{
Expand Down Expand Up @@ -187,10 +194,11 @@ void QgsMapRendererCustomPainterJob::futureFinished()
mRenderingTime = mRenderingStart.elapsed();
QgsDebugMsg( "QPAINTER futureFinished" );

logRenderingTime( mLayerJobs );
logRenderingTime( mLayerJobs, mLabelJob );

// final cleanup
cleanupJobs( mLayerJobs );
cleanupLabelJob( mLabelJob );

emit finished();
}
Expand Down Expand Up @@ -263,8 +271,38 @@ void QgsMapRendererCustomPainterJob::doRender()

QgsDebugMsg( "Done rendering map layers" );

if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelingRenderContext.renderingStopped() )
drawLabeling( mSettings, mLabelingRenderContext, mLabelingEngineV2, mPainter );
if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
{
if ( !mLabelJob.cached )
{
QTime labelTime;
labelTime.start();

if ( mLabelJob.img )
{
QPainter painter;
mLabelJob.img->fill( 0 );
painter.begin( mLabelJob.img );
mLabelJob.context.setPainter( &painter );
drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2, &painter );
painter.end();
}
else
{
drawLabeling( mSettings, mLabelJob.context, mLabelingEngineV2, mPainter );
}

mLabelJob.complete = true;
mLabelJob.renderingTime = labelTime.elapsed();
mLabelJob.participatingLayers = _qgis_listRawToQPointer( mLabelingEngineV2->participatingLayers() );
}
}
if ( mLabelJob.img && mLabelJob.complete )
{
mPainter->setCompositionMode( QPainter::CompositionMode_SourceOver );
mPainter->setOpacity( 1.0 );
mPainter->drawImage( 0, 0, *mLabelJob.img );
}

QgsDebugMsg( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ) );
}
Expand Down
3 changes: 2 additions & 1 deletion src/core/qgsmaprenderercustompainterjob.h
Expand Up @@ -41,6 +41,7 @@ class CORE_EXPORT QgsMapRendererCustomPainterJob : public QgsMapRendererJob
virtual void cancel() override;
virtual void waitForFinished() override;
virtual bool isActive() const override;
virtual bool usedCachedLabels() const override;
virtual QgsLabelingResults* takeLabelingResults() override;

//! @note not available in python bindings
Expand Down Expand Up @@ -83,11 +84,11 @@ class CORE_EXPORT QgsMapRendererCustomPainterJob : public QgsMapRendererJob
QPainter* mPainter;
QFuture<void> mFuture;
QFutureWatcher<void> mFutureWatcher;
QgsRenderContext mLabelingRenderContext;
QgsLabelingEngine* mLabelingEngineV2;

bool mActive;
LayerRenderJobs mLayerJobs;
LabelRenderJob mLabelJob;
bool mRenderSynchronously;

};
Expand Down
123 changes: 118 additions & 5 deletions src/core/qgsmaprendererjob.cpp
Expand Up @@ -33,9 +33,14 @@
#include "qgsvectorlayerrenderer.h"
#include "qgsvectorlayer.h"
#include "qgscsexception.h"
#include "qgslabelingengine.h"
#include "qgsmaplayerlistutils.h"
#include "qgsvectorlayerlabeling.h"

///@cond PRIVATE

const QString QgsMapRendererJob::LABEL_CACHE_ID = QStringLiteral( "_labels_" );

QgsMapRendererJob::QgsMapRendererJob( const QgsMapSettings& settings )
: mSettings( settings )
, mCache( nullptr )
Expand Down Expand Up @@ -66,6 +71,39 @@ const QgsMapSettings& QgsMapRendererJob::mapSettings() const
return mSettings;
}

bool QgsMapRendererJob::prepareLabelCache() const
{
bool canCache = mCache;

// calculate which layers will be labeled
QSet< QgsMapLayer* > labeledLayers;
Q_FOREACH ( const QgsMapLayer* ml, mSettings.layers() )
{
QgsVectorLayer* vl = const_cast< QgsVectorLayer* >( qobject_cast<const QgsVectorLayer *>( ml ) );
if ( vl && QgsPalLabeling::staticWillUseLayer( vl ) )
labeledLayers << vl;
if ( vl && vl->labeling() && vl->labeling()->requiresAdvancedEffects( vl ) )
{
canCache = false;
break;
}
}

if ( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) )
{
// we may need to clear label cache and re-register labeled features - check for that here

// can we reuse the cached label solution?
bool canUseCache = canCache && mCache->dependentLayers( LABEL_CACHE_ID ).toSet() == labeledLayers;
if ( !canUseCache )
{
// no - participating layers have changed
mCache->clearCacheImage( LABEL_CACHE_ID );
}
}
return canCache;
}


bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform& ct, QgsRectangle &extent, QgsRectangle &r2 )
{
Expand Down Expand Up @@ -179,13 +217,15 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsLabelingEn
QListIterator<QgsMapLayer*> li( mSettings.layers() );
li.toBack();

bool cacheValid = false;
if ( mCache )
{
bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
QgsDebugMsg( QString( "CACHE VALID: %1" ).arg( cacheValid ) );
Q_UNUSED( cacheValid );
}

bool requiresLabelRedraw = !( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) );

mGeometryCaches.clear();

while ( li.hasPrevious() )
Expand Down Expand Up @@ -229,8 +269,12 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsLabelingEn
if ( mCache && ml->type() == QgsMapLayer::VectorLayer )
{
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
if ( vl->isEditable() || ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( vl ) ) )
bool requiresLabeling = false;
requiresLabeling = ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( vl ) ) && requiresLabelRedraw;
if ( vl->isEditable() || requiresLabeling )
{
mCache->clearCacheImage( ml->id() );
}
}

layerJobs.append( LayerRenderJob() );
Expand Down Expand Up @@ -312,6 +356,47 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsLabelingEn
return layerJobs;
}

LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter* painter, QgsLabelingEngine* labelingEngine2, bool canUseLabelCache )
{
LabelRenderJob job;
job.context = QgsRenderContext::fromMapSettings( mSettings );
job.context.setPainter( painter );
job.context.setLabelingEngine( labelingEngine2 );
job.context.setExtent( mSettings.visibleExtent() );

// if we can use the cache, let's do it and avoid rendering!
bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
if ( hasCache )
{
job.cached = true;
job.complete = true;
job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
job.context.setPainter( nullptr );
}
else
{
if ( canUseLabelCache && ( mCache || !painter ) )
{
// Flattened image for drawing labels
QImage * mypFlattenedImage = nullptr;
mypFlattenedImage = new QImage( mSettings.outputSize().width(),
mSettings.outputSize().height(),
mSettings.outputImageFormat() );
if ( mypFlattenedImage->isNull() )
{
mErrors.append( Error( QStringLiteral( "labels" ), tr( "Insufficient memory for label image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
delete mypFlattenedImage;
}
else
{
job.img = mypFlattenedImage;
}
}
}

return job;
}


void QgsMapRendererJob::cleanupJobs( LayerRenderJobs& jobs )
{
Expand Down Expand Up @@ -343,13 +428,29 @@ void QgsMapRendererJob::cleanupJobs( LayerRenderJobs& jobs )
}
}


jobs.clear();

updateLayerGeometryCaches();
}

void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob& job )
{
if ( job.img )
{
if ( mCache && !job.cached && !job.context.renderingStopped() )
{
QgsDebugMsg( "caching label result image" );
mCache->setCacheImage( LABEL_CACHE_ID, *job.img, _qgis_listQPointerToRaw( job.participatingLayers ) );
}

QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs )
delete job.img;
job.img = nullptr;
}
}


QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs, const LabelRenderJob& labelJob )
{
QImage image( settings.outputSize(), settings.outputImageFormat() );
image.fill( settings.backgroundColor().rgba() );
Expand All @@ -368,11 +469,21 @@ QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const La
painter.drawImage( 0, 0, *job.img );
}

// IMPORTANT - don't draw labelJob img before the label job is complete,
// as the image is uninitialized and full of garbage before the label job
// commences
if ( labelJob.img && labelJob.complete )
{
painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
painter.setOpacity( 1.0 );
painter.drawImage( 0, 0, *labelJob.img );
}

painter.end();
return image;
}

void QgsMapRendererJob::logRenderingTime( const LayerRenderJobs& jobs )
void QgsMapRendererJob::logRenderingTime( const LayerRenderJobs& jobs, const LabelRenderJob& labelJob )
{
QSettings settings;
if ( !settings.value( QStringLiteral( "/Map/logCanvasRefreshEvent" ), false ).toBool() )
Expand All @@ -382,6 +493,8 @@ void QgsMapRendererJob::logRenderingTime( const LayerRenderJobs& jobs )
Q_FOREACH ( const LayerRenderJob& job, jobs )
elapsed.insert( job.renderingTime, job.layer ? job.layer->id() : QString() );

elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );

QList<int> tt( elapsed.uniqueKeys() );
std::sort( tt.begin(), tt.end(), std::greater<int>() );
Q_FOREACH ( int t, tt )
Expand Down

0 comments on commit 0f5b3fd

Please sign in to comment.