Index: src/app/qgsoptions.cpp =================================================================== --- src/app/qgsoptions.cpp (revision 11830) +++ src/app/qgsoptions.cpp (working copy) @@ -149,6 +149,7 @@ //set the state of the checkboxes chkAntiAliasing->setChecked( settings.value( "/qgis/enable_anti_aliasing", false ).toBool() ); + chkUseRenderCaching->setChecked( settings.value( "/qgis/enable_render_caching", false ).toBool() ); // Slightly awkard here at the settings value is true to use QImage, // but the checkbox is true to use QPixmap @@ -365,6 +366,7 @@ settings.setValue( "/qgis/addPostgisDC", cbxAddPostgisDC->isChecked() ); settings.setValue( "/qgis/new_layers_visible", chkAddedVisibility->isChecked() ); settings.setValue( "/qgis/enable_anti_aliasing", chkAntiAliasing->isChecked() ); + settings.setValue( "/qgis/enable_render_caching", chkUseRenderCaching->isChecked() ); settings.setValue( "/qgis/use_qimage_to_render", !( chkUseQPixmap->isChecked() ) ); settings.setValue( "qgis/capitaliseLayerName", capitaliseCheckBox->isChecked() ); settings.setValue( "qgis/askToSaveProjectChanges", chbAskToSaveProjectChanges->isChecked() ); @@ -530,7 +532,9 @@ // used (we we can. but it then doesn't do anti-aliasing, and this // will confuse people). if ( chkAntiAliasing->isChecked() ) + { chkUseQPixmap->setChecked( false ); + } } @@ -540,7 +544,9 @@ // used (we we can. but it then doesn't do anti-aliasing, and this // will confuse people). if ( chkUseQPixmap->isChecked() ) + { chkAntiAliasing->setChecked( false ); + } } Index: src/app/qgsvectorlayerproperties.cpp =================================================================== --- src/app/qgsvectorlayerproperties.cpp (revision 11830) +++ src/app/qgsvectorlayerproperties.cpp (working copy) @@ -658,6 +658,11 @@ // update symbology emit refreshLegend( layer->getLayerID(), false ); + if ( layer->cacheImage() ) + { + //no need to delete the old one, maplayer will do it if needed + layer->setCacheImage( 0 ); + } layer->triggerRepaint(); // notify the project we've made a change QgsProject::instance()->dirty( true ); Index: src/app/qgisapp.cpp =================================================================== --- src/app/qgisapp.cpp (revision 11830) +++ src/app/qgisapp.cpp (working copy) @@ -4476,6 +4476,9 @@ void QgisApp::refreshMapCanvas() { + //clear all caches first + QgsMapLayerRegistry::instance()->clearAllLayerCaches(); + //then refresh mMapCanvas->refresh(); } Index: src/core/qgsvectorlayer.cpp =================================================================== --- src/core/qgsvectorlayer.cpp (revision 11830) +++ src/core/qgsvectorlayer.cpp (working copy) @@ -2768,6 +2768,9 @@ bool QgsVectorLayer::commitChanges() { bool success = true; + + //clear the cache image so markers dont appear anymore on next draw + setCacheImage( 0 ); mCommitErrors.clear(); Index: src/core/qgsmaprenderer.h =================================================================== --- src/core/qgsmaprenderer.h (revision 11830) +++ src/core/qgsmaprenderer.h (working copy) @@ -199,6 +199,13 @@ //! current extent to be drawn QgsRectangle mExtent; + // + /** Last extent to we drew so we know if we can + used layer render caching or not. Note there are no + accessors for this as it is intended to internal + use only. + @note added in QGIS 1.4 */ + QgsRectangle mLastExtent; //! indicates whether it's map image for overview bool mOverview; Index: src/core/qgsmaplayer.cpp =================================================================== --- src/core/qgsmaplayer.cpp (revision 11830) +++ src/core/qgsmaplayer.cpp (working copy) @@ -75,6 +75,7 @@ mMinScale = 0; mMaxScale = 100000000; mScaleBasedVisibility = false; + mpCacheImage = 0; } @@ -82,6 +83,10 @@ QgsMapLayer::~QgsMapLayer() { delete mCRS; + if ( mpCacheImage ) + { + delete mpCacheImage; + } } QgsMapLayer::LayerType QgsMapLayer::type() const @@ -729,3 +734,13 @@ { return &mUndoStack; } + +void QgsMapLayer::setCacheImage( QImage * thepImage ) +{ + QgsDebugMsg( "cache Image set!" ); + if ( mpCacheImage ) + { + delete mpCacheImage; + } + mpCacheImage = thepImage; +} Index: src/core/qgsmaprenderer.cpp =================================================================== --- src/core/qgsmaprenderer.cpp (revision 11830) +++ src/core/qgsmaprenderer.cpp (working copy) @@ -80,6 +80,8 @@ bool QgsMapRenderer::setExtent( const QgsRectangle& extent ) { + //remember the previous extent + mLastExtent = mExtent; // Don't allow zooms where the current extent is so small that it // can't be accurately represented using a double (which is what @@ -207,6 +209,11 @@ void QgsMapRenderer::render( QPainter* painter ) { + //flag to see if the render context has changed + //since the last time we rendered. If it hasnt changed we can + //take some shortcuts with rendering + bool mySameAsLastFlag = true; + QgsDebugMsg( "========== Rendering ==========" ); if ( mExtent.isEmpty() ) @@ -216,7 +223,9 @@ } if ( mDrawing ) + { return; + } QPaintDevice* thePaintDevice = painter->device(); if ( !thePaintDevice ) @@ -251,10 +260,40 @@ scaleFactor = sceneDpi / 25.4; } double rasterScaleFactor = ( thePaintDevice->logicalDpiX() + thePaintDevice->logicalDpiY() ) / 2.0 / sceneDpi; - mRenderContext.setScaleFactor( scaleFactor ); - mRenderContext.setRasterScaleFactor( rasterScaleFactor ); - mRenderContext.setRendererScale( mScale ); + if ( mRenderContext.rasterScaleFactor() != rasterScaleFactor ) + { + mRenderContext.setRasterScaleFactor( rasterScaleFactor ); + mySameAsLastFlag = false; + } + if ( mRenderContext.scaleFactor() != scaleFactor ) + { + mRenderContext.setScaleFactor( scaleFactor ); + mySameAsLastFlag = false; + } + if ( mRenderContext.rendererScale() != mScale ) + { + //add map scale to render context + mRenderContext.setRendererScale( mScale ); + mySameAsLastFlag = false; + } + if ( mLastExtent != mExtent ) + { + mLastExtent = mExtent; + mySameAsLastFlag = false; + } + // know we know if this render is just a repeat of the last time, we + // can clear caches if it has changed + if ( !mySameAsLastFlag ) + { + //clear the cache pixmap if we changed resolution / extent + QSettings mySettings; + if ( mySettings.value ( "/qgis/enable_render_caching", false ).toBool() ) + { + QgsMapLayerRegistry::instance()->clearAllLayerCaches(); + } + } + bool placeOverlays = false; QgsOverlayObjectPositionManager* overlayManager = overlayManagerFromSettings(); QList allOverlayList; //list of all overlays, used to draw them after layers have been rendered @@ -263,9 +302,6 @@ placeOverlays = true; } - //add map scale to render context - mRenderContext.setRendererScale( mScale ); - // render all layers in the stack, starting at the base QListIterator li( mLayerSet ); li.toBack(); @@ -279,6 +315,10 @@ break; } + // Store the painter in case we need to swap it out for the + // cache painter + QPainter * mypContextPainter = mRenderContext.painter(); + QString layerId = li.previous(); QgsDebugMsg( "Rendering at layer item " + layerId ); @@ -343,17 +383,6 @@ } - if ( scaleRaster ) - { - bk_mapToPixel = mRenderContext.mapToPixel(); - rasterMapToPixel = mRenderContext.mapToPixel(); - rasterMapToPixel.setMapUnitsPerPixel( mRenderContext.mapToPixel().mapUnitsPerPixel() / rasterScaleFactor ); - rasterMapToPixel.setYMaximum( mSize.height() * rasterScaleFactor ); - mRenderContext.setMapToPixel( rasterMapToPixel ); - mRenderContext.painter()->save(); - mRenderContext.painter()->scale( 1.0 / rasterScaleFactor, 1.0 / rasterScaleFactor ); - } - //create overlay objects for features within the view extent if ( ml->type() == QgsMapLayer::VectorLayer && overlayManager ) { @@ -377,11 +406,67 @@ } } + // Force render of layers that are being edited + if ( ml->type() == QgsMapLayer::VectorLayer ) + { + QgsVectorLayer* vl = qobject_cast( ml ); + if ( vl->isEditable() ) + { + ml->setCacheImage( 0 ); + } + } + + QSettings mySettings; + if ( ! split )//render caching does not yet cater for split extents + { + if ( mySettings.value ( "/qgis/enable_render_caching", false ).toBool() ) + { + if ( !mySameAsLastFlag || ml->cacheImage() == 0 ) + { + QgsDebugMsg( "\n\n\nCaching enabled but layer redraw forced by extent change or empty cache\n\n\n" ); + QImage * mypImage = new QImage( mRenderContext.painter()->device()->width(), + mRenderContext.painter()->device()->height(), QImage::Format_ARGB32 ); + mypImage->fill( 0 ); + ml->setCacheImage( mypImage ); //no need to delete the oldone, maplayer does it for you + QPainter * mypPainter = new QPainter( ml->cacheImage() ); + if ( mySettings.value( "/qgis/enable_anti_aliasing", false ).toBool() ) + { + mypPainter->setRenderHint( QPainter::Antialiasing ); + } + mRenderContext.setPainter( mypPainter ); + } + else if ( mySameAsLastFlag ) + { + //draw from cached image + QgsDebugMsg( "\n\n\nCaching enabled --- drawing layer from cached image\n\n\n" ); + mypContextPainter->drawImage( 0,0, *(ml->cacheImage()) ); + disconnect( ml, SIGNAL( drawingProgress( int, int ) ), this, SLOT( onDrawingProgress( int, int ) ) ); + //short circuit as there is nothing else to do... + continue; + } + } + } + + if ( scaleRaster ) + { + bk_mapToPixel = mRenderContext.mapToPixel(); + rasterMapToPixel = mRenderContext.mapToPixel(); + rasterMapToPixel.setMapUnitsPerPixel( mRenderContext.mapToPixel().mapUnitsPerPixel() / rasterScaleFactor ); + rasterMapToPixel.setYMaximum( mSize.height() * rasterScaleFactor ); + mRenderContext.setMapToPixel( rasterMapToPixel ); + mRenderContext.painter()->save(); + mRenderContext.painter()->scale( 1.0 / rasterScaleFactor, 1.0 / rasterScaleFactor ); + } + + if ( !ml->draw( mRenderContext ) ) { emit drawError( ml ); } - + else + { + QgsDebugMsg( "\n\n\nLayer rendered without issues\n\n\n" ); + } if ( split ) { mRenderContext.setExtent( r2 ); @@ -397,12 +482,24 @@ mRenderContext.painter()->restore(); } + if ( mySettings.value ( "/qgis/enable_render_caching", false ).toBool() ) + { + if ( !split ) + { + // composite the cached image into our view and then clean up from caching + // by reinstating the painter as it was swapped out for caching renders + delete mRenderContext.painter(); + mRenderContext.setPainter( mypContextPainter ); + //draw from cached image that we created further up + mypContextPainter->drawImage( 0,0, *(ml->cacheImage()) ); + } + } disconnect( ml, SIGNAL( drawingProgress( int, int ) ), this, SLOT( onDrawingProgress( int, int ) ) ); } - else + else // layer not visible due to scale { QgsDebugMsg( "Layer not rendered because it is not within the defined " - "visibility scale range" ); + "visibility scale range" ); } } // while (li.hasPrevious()) Index: src/core/qgsmaplayerregistry.cpp =================================================================== --- src/core/qgsmaplayerregistry.cpp (revision 11830) +++ src/core/qgsmaplayerregistry.cpp (working copy) @@ -118,6 +118,16 @@ } // QgsMapLayerRegistry::removeAllMapLayers() +//Added in QGIS 1.4 +void QgsMapLayerRegistry::clearAllLayerCaches() +{ + QMap::iterator it; + for ( it = mMapLayers.begin(); it != mMapLayers.end() ; ++it ) + { + //the map layer will take care of deleting the QImage + it.value()->setCacheImage( 0 ); + } +} // QgsMapLayerRegistry::clearAllLayerCaches() QMap & QgsMapLayerRegistry::mapLayers() { Index: src/core/qgsmaplayerregistry.h =================================================================== --- src/core/qgsmaplayerregistry.h (revision 11830) +++ src/core/qgsmaplayerregistry.h (working copy) @@ -82,6 +82,13 @@ */ void removeAllMapLayers(); + /* Clears all layer caches, resetting them to zero and + * freeing up any memory they may have been using. Layer + * caches are used to speed up rendering in certain situations + * see ticket #1974 for more details. + * @note this method was added in QGIS 1.4 + */ + void clearAllLayerCaches(); signals: /** emitted when a layer is removed from the registry Index: src/core/qgsmaplayer.h =================================================================== --- src/core/qgsmaplayer.h (revision 11830) +++ src/core/qgsmaplayer.h (working copy) @@ -24,6 +24,7 @@ #include #include +#include #include "qgsrectangle.h" @@ -263,6 +264,13 @@ /** Return pointer to layer's undo stack */ QUndoStack* undoStack(); + /** Get the QImage used for caching render operations + * @note This method was added in QGIS 1.4 **/ + QImage * cacheImage() { return mpCacheImage; } + /** Set the QImage used for caching render operations + * @note This method was added in QGIS 1.4 **/ + void setCacheImage( QImage * thepImage ); + public slots: /** Event handler for when a coordinate transform fails due to bad vertex error */ @@ -360,8 +368,13 @@ /** A flag that tells us whether to use the above vars to restrict layer visibility */ bool mScaleBasedVisibility; + /** Collection of undoable operations for this layer. **/ QUndoStack mUndoStack; + /**QImage for caching of rendering operations + * @note This property was added in QGIS 1.4 **/ + QImage * mpCacheImage; + }; #endif Index: src/ui/qgsoptionsbase.ui =================================================================== --- src/ui/qgsoptionsbase.ui (revision 11830) +++ src/ui/qgsoptionsbase.ui (working copy) @@ -13,9 +13,7 @@ Options - - - + true @@ -195,21 +193,21 @@ - - + + Qt::Vertical - + QSizePolicy::Fixed - + 20 10 - + @@ -321,6 +319,13 @@ + + + + Use render caching where possible to speed up redraws + + + @@ -369,8 +374,8 @@ - 20 - 40 + 614 + 111 @@ -1308,28 +1313,43 @@ + tabWidget chbAskToSaveProjectChanges chbWarnOldProjectVersion pbnSelectionColour pbnCanvasColor cmbTheme capitaliseCheckBox + cbxLegendClassifiers cbxHideSplash + cbxIdentifyResultsDocked + cbxAttributeTableDocked + cbxAddPostgisDC chkAddedVisibility spinBoxUpdateThreshold + chkUseRenderCaching chkAntiAliasing chkUseQPixmap + cmbIdentifyMode spinBoxIdentifyValue cmbEllipsoid pbnMeasureColour + radMeters + radFeet cmbWheelAction spinZoomFactor + mOverlayAlgorithmComboBox mLineWidthSpinBox mLineColourToolButton mDefaultSnapModeComboBox mDefaultSnappingToleranceSpinBox mSearchRadiusVertexEditSpinBox + mDefaultSnappingToleranceComboBox + mSearchRadiusVertexEditComboBox + mMarkersOnlyForSelectedCheckBox mMarkerStyleComboBox + mMarkerSizeSpinBox + chkDisableAttributeValuesDlg radPromptForProjection radUseProjectProjection radUseGlobalProjection @@ -1337,8 +1357,16 @@ pbnSelectProjection grpLocale cboLocale + grpProxy + leProxyHost + leProxyPort + leProxyUser + leProxyPassword + mProxyTypeComboBox + mAddUrlPushButton + mRemoveUrlPushButton + mExcludeUrlListWidget buttonBox - tabWidget