LCOV - code coverage report
Current view: top level - core - qgsmaprendererparalleljob.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 217 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :   qgsmaprendererparalleljob.cpp
       3                 :            :   --------------------------------------
       4                 :            :   Date                 : December 2013
       5                 :            :   Copyright            : (C) 2013 by Martin Dobias
       6                 :            :   Email                : wonder dot sk at gmail dot com
       7                 :            :  ***************************************************************************
       8                 :            :  *                                                                         *
       9                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      10                 :            :  *   it under the terms of the GNU General Public License as published by  *
      11                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      12                 :            :  *   (at your option) any later version.                                   *
      13                 :            :  *                                                                         *
      14                 :            :  ***************************************************************************/
      15                 :            : 
      16                 :            : #include "qgsmaprendererparalleljob.h"
      17                 :            : 
      18                 :            : #include "qgsfeedback.h"
      19                 :            : #include "qgslabelingengine.h"
      20                 :            : #include "qgslogger.h"
      21                 :            : #include "qgsmaplayerrenderer.h"
      22                 :            : #include "qgsproject.h"
      23                 :            : #include "qgsmaplayer.h"
      24                 :            : #include "qgsmaplayerlistutils.h"
      25                 :            : 
      26                 :            : #include <QtConcurrentMap>
      27                 :            : #include <QtConcurrentRun>
      28                 :            : 
      29                 :          0 : QgsMapRendererParallelJob::QgsMapRendererParallelJob( const QgsMapSettings &settings )
      30                 :          0 :   : QgsMapRendererQImageJob( settings )
      31                 :          0 :   , mStatus( Idle )
      32                 :          0 : {
      33                 :          0 : }
      34                 :            : 
      35                 :          0 : QgsMapRendererParallelJob::~QgsMapRendererParallelJob()
      36                 :          0 : {
      37                 :          0 :   if ( isActive() )
      38                 :            :   {
      39                 :          0 :     cancel();
      40                 :          0 :   }
      41                 :          0 : }
      42                 :            : 
      43                 :          0 : void QgsMapRendererParallelJob::start()
      44                 :            : {
      45                 :          0 :   if ( isActive() )
      46                 :          0 :     return;
      47                 :            : 
      48                 :          0 :   mRenderingStart.start();
      49                 :            : 
      50                 :          0 :   mStatus = RenderingLayers;
      51                 :            : 
      52                 :          0 :   mLabelingEngineV2.reset();
      53                 :            : 
      54                 :          0 :   if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) )
      55                 :            :   {
      56                 :          0 :     mLabelingEngineV2.reset( new QgsDefaultLabelingEngine() );
      57                 :          0 :     mLabelingEngineV2->setMapSettings( mSettings );
      58                 :          0 :   }
      59                 :            : 
      60                 :          0 :   bool canUseLabelCache = prepareLabelCache();
      61                 :          0 :   mLayerJobs = prepareJobs( nullptr, mLabelingEngineV2.get() );
      62                 :          0 :   mLabelJob = prepareLabelingJob( nullptr, mLabelingEngineV2.get(), canUseLabelCache );
      63                 :          0 :   mSecondPassLayerJobs = prepareSecondPassJobs( mLayerJobs, mLabelJob );
      64                 :            : 
      65                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "QThreadPool max thread count is %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ), 2 );
      66                 :            : 
      67                 :            :   // start async job
      68                 :            : 
      69                 :          0 :   connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
      70                 :            : 
      71                 :          0 :   mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
      72                 :          0 :   mFutureWatcher.setFuture( mFuture );
      73                 :          0 : }
      74                 :            : 
      75                 :          0 : void QgsMapRendererParallelJob::cancel()
      76                 :            : {
      77                 :          0 :   if ( !isActive() )
      78                 :          0 :     return;
      79                 :            : 
      80                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
      81                 :            : 
      82                 :          0 :   mLabelJob.context.setRenderingStopped( true );
      83                 :          0 :   for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
      84                 :            :   {
      85                 :          0 :     it->context.setRenderingStopped( true );
      86                 :          0 :     if ( it->renderer && it->renderer->feedback() )
      87                 :          0 :       it->renderer->feedback()->cancel();
      88                 :          0 :   }
      89                 :            : 
      90                 :          0 :   if ( mStatus == RenderingLayers )
      91                 :            :   {
      92                 :          0 :     disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
      93                 :            : 
      94                 :          0 :     mFutureWatcher.waitForFinished();
      95                 :            : 
      96                 :          0 :     renderLayersFinished();
      97                 :          0 :   }
      98                 :            : 
      99                 :          0 :   if ( mStatus == RenderingLabels )
     100                 :            :   {
     101                 :          0 :     disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
     102                 :            : 
     103                 :          0 :     mLabelingFutureWatcher.waitForFinished();
     104                 :            : 
     105                 :          0 :     renderingFinished();
     106                 :          0 :   }
     107                 :            : 
     108                 :          0 :   if ( mStatus == RenderingSecondPass )
     109                 :            :   {
     110                 :          0 :     disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
     111                 :            : 
     112                 :          0 :     mSecondPassFutureWatcher.waitForFinished();
     113                 :            : 
     114                 :          0 :     renderLayersSecondPassFinished();
     115                 :          0 :   }
     116                 :            : 
     117                 :            :   Q_ASSERT( mStatus == Idle );
     118                 :          0 : }
     119                 :            : 
     120                 :          0 : void QgsMapRendererParallelJob::cancelWithoutBlocking()
     121                 :            : {
     122                 :          0 :   if ( !isActive() )
     123                 :          0 :     return;
     124                 :            : 
     125                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
     126                 :            : 
     127                 :          0 :   mLabelJob.context.setRenderingStopped( true );
     128                 :          0 :   for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
     129                 :            :   {
     130                 :          0 :     it->context.setRenderingStopped( true );
     131                 :          0 :     if ( it->renderer && it->renderer->feedback() )
     132                 :          0 :       it->renderer->feedback()->cancel();
     133                 :          0 :   }
     134                 :            : 
     135                 :          0 :   if ( mStatus == RenderingLayers )
     136                 :            :   {
     137                 :          0 :     disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
     138                 :          0 :     connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
     139                 :          0 :   }
     140                 :          0 : }
     141                 :            : 
     142                 :          0 : void QgsMapRendererParallelJob::waitForFinished()
     143                 :            : {
     144                 :          0 :   if ( !isActive() )
     145                 :          0 :     return;
     146                 :            : 
     147                 :          0 :   if ( mStatus == RenderingLayers )
     148                 :            :   {
     149                 :          0 :     disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
     150                 :            : 
     151                 :          0 :     QElapsedTimer t;
     152                 :          0 :     t.start();
     153                 :            : 
     154                 :          0 :     mFutureWatcher.waitForFinished();
     155                 :            : 
     156                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
     157                 :            : 
     158                 :          0 :     renderLayersFinished();
     159                 :          0 :   }
     160                 :            : 
     161                 :          0 :   if ( mStatus == RenderingLabels )
     162                 :            :   {
     163                 :          0 :     disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
     164                 :            : 
     165                 :          0 :     QElapsedTimer t;
     166                 :          0 :     t.start();
     167                 :            : 
     168                 :          0 :     mLabelingFutureWatcher.waitForFinished();
     169                 :            : 
     170                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "waitForFinished (2): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
     171                 :            : 
     172                 :          0 :     renderingFinished();
     173                 :          0 :   }
     174                 :            : 
     175                 :          0 :   if ( mStatus == RenderingSecondPass )
     176                 :            :   {
     177                 :          0 :     disconnect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
     178                 :            : 
     179                 :          0 :     QElapsedTimer t;
     180                 :          0 :     t.start();
     181                 :            : 
     182                 :          0 :     mSecondPassFutureWatcher.waitForFinished();
     183                 :            : 
     184                 :          0 :     QgsDebugMsg( QStringLiteral( "waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ) );
     185                 :            : 
     186                 :          0 :     renderLayersSecondPassFinished();
     187                 :          0 :   }
     188                 :            : 
     189                 :            :   Q_ASSERT( mStatus == Idle );
     190                 :          0 : }
     191                 :            : 
     192                 :          0 : bool QgsMapRendererParallelJob::isActive() const
     193                 :            : {
     194                 :          0 :   return mStatus != Idle;
     195                 :            : }
     196                 :            : 
     197                 :          0 : bool QgsMapRendererParallelJob::usedCachedLabels() const
     198                 :            : {
     199                 :          0 :   return mLabelJob.cached;
     200                 :            : }
     201                 :            : 
     202                 :          0 : QgsLabelingResults *QgsMapRendererParallelJob::takeLabelingResults()
     203                 :            : {
     204                 :          0 :   if ( mLabelingEngineV2 )
     205                 :          0 :     return mLabelingEngineV2->takeResults();
     206                 :            :   else
     207                 :          0 :     return nullptr;
     208                 :          0 : }
     209                 :            : 
     210                 :          0 : QImage QgsMapRendererParallelJob::renderedImage()
     211                 :            : {
     212                 :            :   // if status == Idle we are either waiting for the render to start, OR have finished the render completely.
     213                 :            :   // We can differentiate between those states by checking whether mFinalImage is null -- at the "waiting for
     214                 :            :   // render to start" state mFinalImage has not yet been created.
     215                 :          0 :   const bool jobIsComplete = mStatus == Idle && !mFinalImage.isNull();
     216                 :            : 
     217                 :          0 :   if ( !jobIsComplete )
     218                 :          0 :     return composeImage( mSettings, mLayerJobs, mLabelJob, mCache );
     219                 :            :   else
     220                 :          0 :     return mFinalImage; // when rendering labels or idle
     221                 :          0 : }
     222                 :            : 
     223                 :          0 : void QgsMapRendererParallelJob::renderLayersFinished()
     224                 :            : {
     225                 :            :   Q_ASSERT( mStatus == RenderingLayers );
     226                 :            : 
     227                 :          0 :   LayerRenderJobs::const_iterator it = mLayerJobs.constBegin();
     228                 :          0 :   for ( ; it != mLayerJobs.constEnd(); ++it )
     229                 :            :   {
     230                 :          0 :     if ( !it->errors.isEmpty() )
     231                 :            :     {
     232                 :          0 :       mErrors.append( Error( it->layer->id(), it->errors.join( ',' ) ) );
     233                 :          0 :     }
     234                 :          0 :   }
     235                 :            : 
     236                 :            :   // compose final image for labeling
     237                 :          0 :   if ( mSecondPassLayerJobs.isEmpty() )
     238                 :            :   {
     239                 :          0 :     mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob, mCache );
     240                 :          0 :   }
     241                 :            : 
     242                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "PARALLEL layers finished" ), 2 );
     243                 :            : 
     244                 :          0 :   if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
     245                 :            :   {
     246                 :          0 :     mStatus = RenderingLabels;
     247                 :            : 
     248                 :          0 :     connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
     249                 :            : 
     250                 :            :     // now start rendering of labeling!
     251                 :          0 :     mLabelingFuture = QtConcurrent::run( renderLabelsStatic, this );
     252                 :          0 :     mLabelingFutureWatcher.setFuture( mLabelingFuture );
     253                 :          0 :     emit renderingLayersFinished();
     254                 :          0 :   }
     255                 :            :   else
     256                 :            :   {
     257                 :          0 :     renderingFinished();
     258                 :            :   }
     259                 :          0 : }
     260                 :            : 
     261                 :            : #define DEBUG_RENDERING 0
     262                 :            : 
     263                 :          0 : void QgsMapRendererParallelJob::renderingFinished()
     264                 :            : {
     265                 :            : #if DEBUG_RENDERING
     266                 :            :   int i = 0;
     267                 :            :   for ( LayerRenderJob &job : mLayerJobs )
     268                 :            :   {
     269                 :            :     if ( job.img )
     270                 :            :     {
     271                 :            :       job.img->save( QString( "/tmp/first_pass_%1.png" ).arg( i ) );
     272                 :            :     }
     273                 :            :     if ( job.maskPass.image )
     274                 :            :     {
     275                 :            :       job.maskPass.image->save( QString( "/tmp/first_pass_%1_mask.png" ).arg( i ) );
     276                 :            :     }
     277                 :            :     i++;
     278                 :            :   }
     279                 :            :   if ( mLabelJob.img )
     280                 :            :   {
     281                 :            :     mLabelJob.img->save( QString( "/tmp/labels.png" ) );
     282                 :            :   }
     283                 :            :   if ( mLabelJob.maskImage )
     284                 :            :   {
     285                 :            :     mLabelJob.maskImage->save( QString( "/tmp/labels_mask.png" ) );
     286                 :            :   }
     287                 :            : #endif
     288                 :          0 :   if ( ! mSecondPassLayerJobs.isEmpty() )
     289                 :            :   {
     290                 :          0 :     mStatus = RenderingSecondPass;
     291                 :            :     // We have a second pass to do.
     292                 :          0 :     mSecondPassFuture = QtConcurrent::map( mSecondPassLayerJobs, renderLayerStatic );
     293                 :          0 :     mSecondPassFutureWatcher.setFuture( mSecondPassFuture );
     294                 :          0 :     connect( &mSecondPassFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersSecondPassFinished );
     295                 :          0 :   }
     296                 :            :   else
     297                 :            :   {
     298                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "PARALLEL finished" ), 2 );
     299                 :            : 
     300                 :          0 :     logRenderingTime( mLayerJobs, mSecondPassLayerJobs, mLabelJob );
     301                 :            : 
     302                 :          0 :     cleanupJobs( mLayerJobs );
     303                 :            : 
     304                 :          0 :     cleanupLabelJob( mLabelJob );
     305                 :            : 
     306                 :          0 :     mStatus = Idle;
     307                 :            : 
     308                 :          0 :     mRenderingTime = mRenderingStart.elapsed();
     309                 :            : 
     310                 :          0 :     emit finished();
     311                 :            :   }
     312                 :          0 : }
     313                 :            : 
     314                 :          0 : void QgsMapRendererParallelJob::renderLayersSecondPassFinished()
     315                 :            : {
     316                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "PARALLEL finished" ), 2 );
     317                 :            : 
     318                 :            :   // compose second pass images into first pass images
     319                 :          0 :   composeSecondPass( mSecondPassLayerJobs, mLabelJob );
     320                 :            : 
     321                 :            :   // compose final image
     322                 :          0 :   mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
     323                 :            : 
     324                 :          0 :   logRenderingTime( mLayerJobs, mSecondPassLayerJobs, mLabelJob );
     325                 :            : 
     326                 :          0 :   cleanupJobs( mLayerJobs );
     327                 :            : 
     328                 :          0 :   cleanupSecondPassJobs( mSecondPassLayerJobs );
     329                 :            : 
     330                 :          0 :   cleanupLabelJob( mLabelJob );
     331                 :            : 
     332                 :          0 :   mStatus = Idle;
     333                 :            : 
     334                 :          0 :   mRenderingTime = mRenderingStart.elapsed();
     335                 :            : 
     336                 :          0 :   emit finished();
     337                 :          0 : }
     338                 :            : 
     339                 :            : /*
     340                 :            :  * See section "Smarter Map Redraws"
     341                 :            :  * in https://github.com/qgis/QGIS-Enhancement-Proposals/issues/181
     342                 :            :  */
     343                 :            : // #define SIMULATE_SLOW_RENDERER
     344                 :            : 
     345                 :          0 : void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
     346                 :            : {
     347                 :          0 :   if ( job.context.renderingStopped() )
     348                 :          0 :     return;
     349                 :            : 
     350                 :          0 :   if ( job.cached )
     351                 :          0 :     return;
     352                 :            : 
     353                 :          0 :   if ( job.img )
     354                 :            :   {
     355                 :          0 :     job.img->fill( 0 );
     356                 :          0 :     job.imageInitialized = true;
     357                 :          0 :   }
     358                 :            : 
     359                 :          0 :   QElapsedTimer t;
     360                 :          0 :   t.start();
     361                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "job %1 start (layer %2)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.layerId ), 2 );
     362                 :            :   try
     363                 :            :   {
     364                 :            : #ifdef SIMULATE_SLOW_RENDERER
     365                 :            :     QThread::sleep( 1 );
     366                 :            : #endif
     367                 :          0 :     job.completed = job.renderer->render();
     368                 :          0 :   }
     369                 :            :   catch ( QgsException &e )
     370                 :            :   {
     371                 :          0 :     Q_UNUSED( e )
     372                 :          0 :     QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
     373                 :          0 :   }
     374                 :            :   catch ( std::exception &e )
     375                 :            :   {
     376                 :          0 :     Q_UNUSED( e )
     377                 :          0 :     QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
     378                 :          0 :   }
     379                 :            :   catch ( ... )
     380                 :            :   {
     381                 :          0 :     QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
     382                 :          0 :   }
     383                 :            : 
     384                 :          0 :   job.errors = job.renderer->errors();
     385                 :          0 :   job.renderingTime += t.elapsed();
     386                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "job %1 end [%2 ms] (layer %3)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layerId ), 2 );
     387                 :          0 : }
     388                 :            : 
     389                 :            : 
     390                 :          0 : void QgsMapRendererParallelJob::renderLabelsStatic( QgsMapRendererParallelJob *self )
     391                 :            : {
     392                 :          0 :   LabelRenderJob &job = self->mLabelJob;
     393                 :            : 
     394                 :          0 :   if ( !job.cached )
     395                 :            :   {
     396                 :          0 :     QElapsedTimer labelTime;
     397                 :          0 :     labelTime.start();
     398                 :            : 
     399                 :          0 :     QPainter painter;
     400                 :          0 :     if ( job.img )
     401                 :            :     {
     402                 :          0 :       job.img->fill( 0 );
     403                 :          0 :       painter.begin( job.img );
     404                 :          0 :     }
     405                 :            :     else
     406                 :            :     {
     407                 :          0 :       painter.begin( &self->mFinalImage );
     408                 :            :     }
     409                 :            : 
     410                 :            :     // draw the labels!
     411                 :            :     try
     412                 :            :     {
     413                 :          0 :       drawLabeling( job.context, self->mLabelingEngineV2.get(), &painter );
     414                 :          0 :     }
     415                 :            :     catch ( QgsException &e )
     416                 :            :     {
     417                 :          0 :       Q_UNUSED( e )
     418                 :          0 :       QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
     419                 :          0 :     }
     420                 :            :     catch ( std::exception &e )
     421                 :            :     {
     422                 :          0 :       Q_UNUSED( e )
     423                 :          0 :       QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
     424                 :          0 :     }
     425                 :            :     catch ( ... )
     426                 :            :     {
     427                 :          0 :       QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
     428                 :          0 :     }
     429                 :            : 
     430                 :          0 :     painter.end();
     431                 :            : 
     432                 :          0 :     job.renderingTime = labelTime.elapsed();
     433                 :          0 :     job.complete = true;
     434                 :          0 :     job.participatingLayers = _qgis_listRawToQPointer( self->mLabelingEngineV2->participatingLayers() );
     435                 :          0 :     if ( job.img )
     436                 :            :     {
     437                 :          0 :       self->mFinalImage = composeImage( self->mSettings, self->mLayerJobs, self->mLabelJob );
     438                 :          0 :     }
     439                 :          0 :   }
     440                 :          0 : }
     441                 :            : 

Generated by: LCOV version 1.14