Skip to content

Commit e4a1651

Browse files
committed
Bring back render caching
1 parent 0d2fbbe commit e4a1651

9 files changed

+285
-32
lines changed

src/app/qgisapp.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,8 @@ void QgisApp::createOverview()
20692069
int action = mySettings.value( "/qgis/wheel_action", 2 ).toInt();
20702070
double zoomFactor = mySettings.value( "/qgis/zoom_factor", 2 ).toDouble();
20712071
mMapCanvas->setWheelAction(( QgsMapCanvas::WheelAction ) action, zoomFactor );
2072+
2073+
mMapCanvas->setCachingEnabled( mySettings.value( "/qgis/enable_render_caching", false ).toBool() );
20722074
}
20732075

20742076
void QgisApp::addDockWidget( Qt::DockWidgetArea theArea, QDockWidget * thepDockWidget )
@@ -6597,6 +6599,8 @@ void QgisApp::options()
65976599
double zoomFactor = mySettings.value( "/qgis/zoom_factor", 2 ).toDouble();
65986600
mMapCanvas->setWheelAction(( QgsMapCanvas::WheelAction ) action, zoomFactor );
65996601

6602+
mMapCanvas->setCachingEnabled( mySettings.value( "/qgis/enable_render_caching", false ).toBool() );
6603+
66006604
//do we need this? TS
66016605
mMapCanvas->refresh();
66026606

src/core/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
SET(QGIS_CORE_SRCS
55

6+
qgsmaprenderercache.cpp
67
qgsxmlutils.cpp
78
qgsmapsettings.cpp
89
qgsmaprendererjob.cpp
@@ -300,6 +301,7 @@ ADD_BISON_FILES(QGIS_CORE_SRCS qgsexpressionparser.yy)
300301

301302
SET(QGIS_CORE_MOC_HDRS
302303

304+
qgsmaprenderercache.h
303305
qgsmapsettings.h
304306
qgsmaprendererjob.h
305307

src/core/qgsmaprenderercache.cpp

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
2+
#include "qgsmaprenderercache.h"
3+
4+
5+
QgsMapRendererCache::QgsMapRendererCache()
6+
{
7+
clear();
8+
}
9+
10+
void QgsMapRendererCache::clear()
11+
{
12+
QMutexLocker lock( &mMutex );
13+
mExtent.setMinimal();
14+
mScale = 0;
15+
mCachedImages.clear();
16+
}
17+
18+
bool QgsMapRendererCache::init( QgsRectangle extent, double scale )
19+
{
20+
QMutexLocker lock( &mMutex );
21+
22+
// check whether the params are the same
23+
if (extent == mExtent &&
24+
scale == mScale )
25+
return true;
26+
27+
// set new params
28+
mExtent = extent;
29+
mScale = scale;
30+
31+
// invalidate cache
32+
mCachedImages.clear();
33+
34+
return false;
35+
}
36+
37+
void QgsMapRendererCache::setCacheImage( QString layerId, const QImage& img )
38+
{
39+
QMutexLocker lock( &mMutex );
40+
mCachedImages[layerId] = img;
41+
}
42+
43+
QImage QgsMapRendererCache::cacheImage( QString layerId )
44+
{
45+
QMutexLocker lock( &mMutex );
46+
return mCachedImages.value( layerId );
47+
}
48+
49+
void QgsMapRendererCache::layerDataChanged()
50+
{
51+
// TODO!
52+
qDebug("nothing here yet");
53+
}
54+
55+

src/core/qgsmaprenderercache.h

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#ifndef QGSMAPRENDERERCACHE_H
2+
#define QGSMAPRENDERERCACHE_H
3+
4+
#include <QMap>
5+
#include <QImage>
6+
#include <QMutex>
7+
8+
#include "qgsrectangle.h"
9+
10+
11+
/**
12+
* This class is responsible for keeping cache of rendered images of individual layers.
13+
*
14+
* The class is thread-safe (multiple classes can access the same instance safely).
15+
*
16+
* @note added in 2.1
17+
*/
18+
class CORE_EXPORT QgsMapRendererCache : public QObject
19+
{
20+
Q_OBJECT
21+
public:
22+
23+
QgsMapRendererCache();
24+
25+
//! invalidate the cache contents
26+
void clear();
27+
28+
//! initialize cache: set new parameters and erase cache if parameters have changed
29+
//! @return flag whether the parameters are the same as last time
30+
bool init( QgsRectangle extent, double scale );
31+
32+
//! set cached image for the specified layer ID
33+
void setCacheImage( QString layerId, const QImage& img );
34+
35+
//! get cached image for the specified layer ID. Returns null image if it is not cached.
36+
QImage cacheImage( QString layerId );
37+
38+
public slots:
39+
//! remove layer (that emitted the signal) from the cache
40+
void layerDataChanged();
41+
42+
protected:
43+
QMutex mMutex;
44+
QgsRectangle mExtent;
45+
double mScale;
46+
QMap<QString, QImage> mCachedImages;
47+
};
48+
49+
50+
#endif // QGSMAPRENDERERCACHE_H

src/core/qgsmaprendererjob.cpp

+80-30
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
#include "qgsmaplayer.h"
1212
#include "qgsmaplayerregistry.h"
1313
#include "qgsmaplayerrenderer.h"
14+
#include "qgsmaprenderercache.h"
1415
#include "qgspallabeling.h"
1516

1617

1718
QgsMapRendererJob::QgsMapRendererJob( const QgsMapSettings& settings )
1819
: mSettings(settings)
20+
, mCache( 0 )
1921
{
2022
}
2123

@@ -24,6 +26,11 @@ QgsMapRendererJob::Errors QgsMapRendererJob::errors() const
2426
return mErrors;
2527
}
2628

29+
void QgsMapRendererJob::setCache( QgsMapRendererCache* cache )
30+
{
31+
mCache = cache;
32+
}
33+
2734

2835
QgsMapRendererQImageJob::QgsMapRendererQImageJob( const QgsMapSettings& settings )
2936
: QgsMapRendererJob( settings )
@@ -74,6 +81,7 @@ void QgsMapRendererSequentialJob::start()
7481
mPainter = new QPainter(&mImage);
7582

7683
mInternalJob = new QgsMapRendererCustomPainterJob(mSettings, mPainter);
84+
mInternalJob->setCache( mCache );
7785

7886
connect(mInternalJob, SIGNAL(finished()), SLOT(internalFinished()));
7987

@@ -115,7 +123,11 @@ QgsLabelingResults* QgsMapRendererSequentialJob::takeLabelingResults()
115123

116124
QImage QgsMapRendererSequentialJob::renderedImage()
117125
{
118-
return mImage;
126+
if ( isActive() && mCache )
127+
// this will allow immediate display of cached layers and at the same time updates of the layer being rendered
128+
return composeImage( mSettings, mInternalJob->jobs() );
129+
else
130+
return mImage;
119131
}
120132

121133

@@ -309,7 +321,8 @@ void QgsMapRendererCustomPainterJob::doRender()
309321
mPainter->setCompositionMode( job.blendMode );
310322
}
311323

312-
job.renderer->render();
324+
if ( !job.cached )
325+
job.renderer->render();
313326

314327
if ( job.img )
315328
{
@@ -496,6 +509,12 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsPalLabelin
496509
QListIterator<QString> li( mSettings.layers() );
497510
li.toBack();
498511

512+
if ( mCache )
513+
{
514+
bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
515+
qDebug("CACHE VALID: %d", cacheValid);
516+
}
517+
499518
while ( li.hasPrevious() )
500519
{
501520
QString layerId = li.previous();
@@ -542,38 +561,56 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsPalLabelin
542561
}
543562
}
544563

545-
// Flattened image for drawing when a blending mode is set
546-
QImage * mypFlattenedImage = 0;
547-
548-
// If we are drawing with an alternative blending mode then we need to render to a separate image
549-
// before compositing this on the map. This effectively flattens the layer and prevents
550-
// blending occuring between objects on the layer
551-
if ( !painter || needTemporaryImage( ml ) )
564+
// Force render of layers that are being edited
565+
// or if there's a labeling engine that needs the layer to register features
566+
if ( mCache && ml->type() == QgsMapLayer::VectorLayer )
552567
{
553-
mypFlattenedImage = new QImage( mSettings.outputSize().width(),
554-
mSettings.outputSize().height(), QImage::Format_ARGB32 );
555-
if ( mypFlattenedImage->isNull() )
556-
{
557-
mErrors.append( Error( layerId, "Insufficient memory for image " + QString::number( mSettings.outputSize().width() ) + "x" + QString::number( mSettings.outputSize().height() ) ) );
558-
delete mypFlattenedImage;
559-
continue;
560-
}
561-
mypFlattenedImage->fill( 0 );
568+
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
569+
if ( vl->isEditable() || ( labelingEngine && labelingEngine->willUseLayer( vl ) ) )
570+
mCache->setCacheImage( ml->id(), QImage() );
562571
}
563572

564573
layerJobs.append( LayerRenderJob() );
565574
LayerRenderJob& job = layerJobs.last();
575+
job.cached = false;
566576
job.img = 0;
567577
job.blendMode = ml->blendMode();
578+
job.layerId = ml->id();
568579

569580
job.context = QgsRenderContext::fromMapSettings( mSettings );
570581
job.context.setPainter( painter );
571582
job.context.setLabelingEngine( labelingEngine );
572583
job.context.setCoordinateTransform( ct );
573584
job.context.setExtent( r1 );
574585

575-
if ( mypFlattenedImage )
586+
// if we can use the cache, let's do it and avoid rendering!
587+
if ( mCache && !mCache->cacheImage( ml->id() ).isNull() )
576588
{
589+
job.cached = true;
590+
job.img = new QImage( mCache->cacheImage( ml->id() ) );
591+
job.renderer = 0;
592+
job.context.setPainter( 0 );
593+
continue;
594+
}
595+
596+
// If we are drawing with an alternative blending mode then we need to render to a separate image
597+
// before compositing this on the map. This effectively flattens the layer and prevents
598+
// blending occuring between objects on the layer
599+
if ( mCache || !painter || needTemporaryImage( ml ) )
600+
{
601+
// Flattened image for drawing when a blending mode is set
602+
QImage * mypFlattenedImage = 0;
603+
mypFlattenedImage = new QImage( mSettings.outputSize().width(),
604+
mSettings.outputSize().height(), QImage::Format_ARGB32_Premultiplied );
605+
if ( mypFlattenedImage->isNull() )
606+
{
607+
mErrors.append( Error( layerId, "Insufficient memory for image " + QString::number( mSettings.outputSize().width() ) + "x" + QString::number( mSettings.outputSize().height() ) ) );
608+
delete mypFlattenedImage;
609+
layerJobs.removeLast();
610+
continue;
611+
}
612+
mypFlattenedImage->fill( 0 );
613+
577614
job.img = mypFlattenedImage;
578615
QPainter* mypPainter = new QPainter( job.img );
579616
mypPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
@@ -603,18 +640,28 @@ void QgsMapRendererJob::cleanupJobs( LayerRenderJobs& jobs )
603640
{
604641
LayerRenderJob& job = *it;
605642
if ( job.img )
606-
{
643+
{
607644
delete job.context.painter();
608645
job.context.setPainter( 0 );
646+
647+
if ( mCache && !job.cached && !job.context.renderingStopped() )
648+
{
649+
qDebug("caching image for %s", job.layerId.toAscii().data());
650+
mCache->setCacheImage( job.layerId, *job.img );
651+
}
652+
609653
delete job.img;
610654
job.img = 0;
611655
}
612656

613-
foreach ( QString message, job.renderer->errors() )
614-
mErrors.append( Error( job.renderer->layerID(), message ) );
657+
if ( job.renderer )
658+
{
659+
foreach ( QString message, job.renderer->errors() )
660+
mErrors.append( Error( job.renderer->layerID(), message ) );
615661

616-
delete job.renderer;
617-
job.renderer = 0;
662+
delete job.renderer;
663+
job.renderer = 0;
664+
}
618665
}
619666

620667
jobs.clear();
@@ -752,7 +799,7 @@ QgsLabelingResults* QgsMapRendererParallelJob::takeLabelingResults()
752799
QImage QgsMapRendererParallelJob::renderedImage()
753800
{
754801
if ( mStatus == RenderingLayers )
755-
return composeImage();
802+
return composeImage( mSettings, mLayerJobs );
756803
else
757804
return mFinalImage; // when rendering labels or idle
758805
}
@@ -762,7 +809,7 @@ void QgsMapRendererParallelJob::renderLayersFinished()
762809
Q_ASSERT( mStatus == RenderingLayers );
763810

764811
// compose final image
765-
mFinalImage = composeImage();
812+
mFinalImage = composeImage( mSettings, mLayerJobs );
766813

767814
cleanupJobs( mLayerJobs );
768815

@@ -798,6 +845,9 @@ void QgsMapRendererParallelJob::renderLayerStatic(LayerRenderJob& job)
798845
if ( job.context.renderingStopped() )
799846
return;
800847

848+
if ( job.cached )
849+
return;
850+
801851
QTime t;
802852
t.start();
803853
QgsDebugMsg( QString("job %1 start").arg( (ulong) &job, 0, 16 ) );
@@ -817,14 +867,14 @@ void QgsMapRendererParallelJob::renderLabelsStatic(QgsMapRendererParallelJob* se
817867
}
818868

819869

820-
QImage QgsMapRendererParallelJob::composeImage()
870+
QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs )
821871
{
822-
QImage image( mSettings.outputSize(), QImage::Format_ARGB32_Premultiplied );
823-
image.fill( mSettings.backgroundColor().rgb() );
872+
QImage image( settings.outputSize(), QImage::Format_ARGB32_Premultiplied );
873+
image.fill( settings.backgroundColor().rgb() );
824874

825875
QPainter painter(&image);
826876

827-
for (LayerRenderJobs::const_iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it)
877+
for (LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it)
828878
{
829879
const LayerRenderJob& job = *it;
830880

0 commit comments

Comments
 (0)