Skip to content

Commit

Permalink
Bring back render caching
Browse files Browse the repository at this point in the history
  • Loading branch information
wonder-sk committed Dec 9, 2013
1 parent 0d2fbbe commit e4a1651
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 32 deletions.
4 changes: 4 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -2069,6 +2069,8 @@ void QgisApp::createOverview()
int action = mySettings.value( "/qgis/wheel_action", 2 ).toInt();
double zoomFactor = mySettings.value( "/qgis/zoom_factor", 2 ).toDouble();
mMapCanvas->setWheelAction(( QgsMapCanvas::WheelAction ) action, zoomFactor );

mMapCanvas->setCachingEnabled( mySettings.value( "/qgis/enable_render_caching", false ).toBool() );
}

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

mMapCanvas->setCachingEnabled( mySettings.value( "/qgis/enable_render_caching", false ).toBool() );

//do we need this? TS
mMapCanvas->refresh();

Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -3,6 +3,7 @@

SET(QGIS_CORE_SRCS

qgsmaprenderercache.cpp
qgsxmlutils.cpp
qgsmapsettings.cpp
qgsmaprendererjob.cpp
Expand Down Expand Up @@ -300,6 +301,7 @@ ADD_BISON_FILES(QGIS_CORE_SRCS qgsexpressionparser.yy)

SET(QGIS_CORE_MOC_HDRS

qgsmaprenderercache.h
qgsmapsettings.h
qgsmaprendererjob.h

Expand Down
55 changes: 55 additions & 0 deletions src/core/qgsmaprenderercache.cpp
@@ -0,0 +1,55 @@

#include "qgsmaprenderercache.h"


QgsMapRendererCache::QgsMapRendererCache()
{
clear();
}

void QgsMapRendererCache::clear()
{
QMutexLocker lock( &mMutex );
mExtent.setMinimal();
mScale = 0;
mCachedImages.clear();
}

bool QgsMapRendererCache::init( QgsRectangle extent, double scale )
{
QMutexLocker lock( &mMutex );

// check whether the params are the same
if (extent == mExtent &&
scale == mScale )
return true;

// set new params
mExtent = extent;
mScale = scale;

// invalidate cache
mCachedImages.clear();

return false;
}

void QgsMapRendererCache::setCacheImage( QString layerId, const QImage& img )
{
QMutexLocker lock( &mMutex );
mCachedImages[layerId] = img;
}

QImage QgsMapRendererCache::cacheImage( QString layerId )
{
QMutexLocker lock( &mMutex );
return mCachedImages.value( layerId );
}

void QgsMapRendererCache::layerDataChanged()
{
// TODO!
qDebug("nothing here yet");
}


50 changes: 50 additions & 0 deletions src/core/qgsmaprenderercache.h
@@ -0,0 +1,50 @@
#ifndef QGSMAPRENDERERCACHE_H
#define QGSMAPRENDERERCACHE_H

#include <QMap>
#include <QImage>
#include <QMutex>

#include "qgsrectangle.h"


/**
* This class is responsible for keeping cache of rendered images of individual layers.
*
* The class is thread-safe (multiple classes can access the same instance safely).
*
* @note added in 2.1
*/
class CORE_EXPORT QgsMapRendererCache : public QObject
{
Q_OBJECT
public:

QgsMapRendererCache();

//! invalidate the cache contents
void clear();

//! initialize cache: set new parameters and erase cache if parameters have changed
//! @return flag whether the parameters are the same as last time
bool init( QgsRectangle extent, double scale );

//! set cached image for the specified layer ID
void setCacheImage( QString layerId, const QImage& img );

//! get cached image for the specified layer ID. Returns null image if it is not cached.
QImage cacheImage( QString layerId );

public slots:
//! remove layer (that emitted the signal) from the cache
void layerDataChanged();

protected:
QMutex mMutex;
QgsRectangle mExtent;
double mScale;
QMap<QString, QImage> mCachedImages;
};


#endif // QGSMAPRENDERERCACHE_H
110 changes: 80 additions & 30 deletions src/core/qgsmaprendererjob.cpp
Expand Up @@ -11,11 +11,13 @@
#include "qgsmaplayer.h"
#include "qgsmaplayerregistry.h"
#include "qgsmaplayerrenderer.h"
#include "qgsmaprenderercache.h"
#include "qgspallabeling.h"


QgsMapRendererJob::QgsMapRendererJob( const QgsMapSettings& settings )
: mSettings(settings)
, mCache( 0 )
{
}

Expand All @@ -24,6 +26,11 @@ QgsMapRendererJob::Errors QgsMapRendererJob::errors() const
return mErrors;
}

void QgsMapRendererJob::setCache( QgsMapRendererCache* cache )
{
mCache = cache;
}


QgsMapRendererQImageJob::QgsMapRendererQImageJob( const QgsMapSettings& settings )
: QgsMapRendererJob( settings )
Expand Down Expand Up @@ -74,6 +81,7 @@ void QgsMapRendererSequentialJob::start()
mPainter = new QPainter(&mImage);

mInternalJob = new QgsMapRendererCustomPainterJob(mSettings, mPainter);
mInternalJob->setCache( mCache );

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

Expand Down Expand Up @@ -115,7 +123,11 @@ QgsLabelingResults* QgsMapRendererSequentialJob::takeLabelingResults()

QImage QgsMapRendererSequentialJob::renderedImage()
{
return mImage;
if ( isActive() && mCache )
// this will allow immediate display of cached layers and at the same time updates of the layer being rendered
return composeImage( mSettings, mInternalJob->jobs() );
else
return mImage;
}


Expand Down Expand Up @@ -309,7 +321,8 @@ void QgsMapRendererCustomPainterJob::doRender()
mPainter->setCompositionMode( job.blendMode );
}

job.renderer->render();
if ( !job.cached )
job.renderer->render();

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

if ( mCache )
{
bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
qDebug("CACHE VALID: %d", cacheValid);
}

while ( li.hasPrevious() )
{
QString layerId = li.previous();
Expand Down Expand Up @@ -542,38 +561,56 @@ LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsPalLabelin
}
}

// Flattened image for drawing when a blending mode is set
QImage * mypFlattenedImage = 0;

// If we are drawing with an alternative blending mode then we need to render to a separate image
// before compositing this on the map. This effectively flattens the layer and prevents
// blending occuring between objects on the layer
if ( !painter || needTemporaryImage( ml ) )
// Force render of layers that are being edited
// or if there's a labeling engine that needs the layer to register features
if ( mCache && ml->type() == QgsMapLayer::VectorLayer )
{
mypFlattenedImage = new QImage( mSettings.outputSize().width(),
mSettings.outputSize().height(), QImage::Format_ARGB32 );
if ( mypFlattenedImage->isNull() )
{
mErrors.append( Error( layerId, "Insufficient memory for image " + QString::number( mSettings.outputSize().width() ) + "x" + QString::number( mSettings.outputSize().height() ) ) );
delete mypFlattenedImage;
continue;
}
mypFlattenedImage->fill( 0 );
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
if ( vl->isEditable() || ( labelingEngine && labelingEngine->willUseLayer( vl ) ) )
mCache->setCacheImage( ml->id(), QImage() );
}

layerJobs.append( LayerRenderJob() );
LayerRenderJob& job = layerJobs.last();
job.cached = false;
job.img = 0;
job.blendMode = ml->blendMode();
job.layerId = ml->id();

job.context = QgsRenderContext::fromMapSettings( mSettings );
job.context.setPainter( painter );
job.context.setLabelingEngine( labelingEngine );
job.context.setCoordinateTransform( ct );
job.context.setExtent( r1 );

if ( mypFlattenedImage )
// if we can use the cache, let's do it and avoid rendering!
if ( mCache && !mCache->cacheImage( ml->id() ).isNull() )
{
job.cached = true;
job.img = new QImage( mCache->cacheImage( ml->id() ) );
job.renderer = 0;
job.context.setPainter( 0 );
continue;
}

// If we are drawing with an alternative blending mode then we need to render to a separate image
// before compositing this on the map. This effectively flattens the layer and prevents
// blending occuring between objects on the layer
if ( mCache || !painter || needTemporaryImage( ml ) )
{
// Flattened image for drawing when a blending mode is set
QImage * mypFlattenedImage = 0;
mypFlattenedImage = new QImage( mSettings.outputSize().width(),
mSettings.outputSize().height(), QImage::Format_ARGB32_Premultiplied );
if ( mypFlattenedImage->isNull() )
{
mErrors.append( Error( layerId, "Insufficient memory for image " + QString::number( mSettings.outputSize().width() ) + "x" + QString::number( mSettings.outputSize().height() ) ) );
delete mypFlattenedImage;
layerJobs.removeLast();
continue;
}
mypFlattenedImage->fill( 0 );

job.img = mypFlattenedImage;
QPainter* mypPainter = new QPainter( job.img );
mypPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
Expand Down Expand Up @@ -603,18 +640,28 @@ void QgsMapRendererJob::cleanupJobs( LayerRenderJobs& jobs )
{
LayerRenderJob& job = *it;
if ( job.img )
{
{
delete job.context.painter();
job.context.setPainter( 0 );

if ( mCache && !job.cached && !job.context.renderingStopped() )
{
qDebug("caching image for %s", job.layerId.toAscii().data());
mCache->setCacheImage( job.layerId, *job.img );
}

delete job.img;
job.img = 0;
}

foreach ( QString message, job.renderer->errors() )
mErrors.append( Error( job.renderer->layerID(), message ) );
if ( job.renderer )
{
foreach ( QString message, job.renderer->errors() )
mErrors.append( Error( job.renderer->layerID(), message ) );

delete job.renderer;
job.renderer = 0;
delete job.renderer;
job.renderer = 0;
}
}

jobs.clear();
Expand Down Expand Up @@ -752,7 +799,7 @@ QgsLabelingResults* QgsMapRendererParallelJob::takeLabelingResults()
QImage QgsMapRendererParallelJob::renderedImage()
{
if ( mStatus == RenderingLayers )
return composeImage();
return composeImage( mSettings, mLayerJobs );
else
return mFinalImage; // when rendering labels or idle
}
Expand All @@ -762,7 +809,7 @@ void QgsMapRendererParallelJob::renderLayersFinished()
Q_ASSERT( mStatus == RenderingLayers );

// compose final image
mFinalImage = composeImage();
mFinalImage = composeImage( mSettings, mLayerJobs );

cleanupJobs( mLayerJobs );

Expand Down Expand Up @@ -798,6 +845,9 @@ void QgsMapRendererParallelJob::renderLayerStatic(LayerRenderJob& job)
if ( job.context.renderingStopped() )
return;

if ( job.cached )
return;

QTime t;
t.start();
QgsDebugMsg( QString("job %1 start").arg( (ulong) &job, 0, 16 ) );
Expand All @@ -817,14 +867,14 @@ void QgsMapRendererParallelJob::renderLabelsStatic(QgsMapRendererParallelJob* se
}


QImage QgsMapRendererParallelJob::composeImage()
QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs )
{
QImage image( mSettings.outputSize(), QImage::Format_ARGB32_Premultiplied );
image.fill( mSettings.backgroundColor().rgb() );
QImage image( settings.outputSize(), QImage::Format_ARGB32_Premultiplied );
image.fill( settings.backgroundColor().rgb() );

QPainter painter(&image);

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

Expand Down

0 comments on commit e4a1651

Please sign in to comment.