File indexing completed on 2024-05-19 04:28:51

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_animation_cache_populator.h"
0008 
0009 #include <functional>
0010 
0011 #include <QTimer>
0012 #include <QMutex>
0013 #include <QtConcurrent>
0014 
0015 #include "kis_config.h"
0016 #include "kis_config_notifier.h"
0017 #include "KisPart.h"
0018 #include "KisDocument.h"
0019 #include "kis_image.h"
0020 #include "kis_image_animation_interface.h"
0021 #include "kis_canvas2.h"
0022 #include "kis_time_span.h"
0023 #include "kis_animation_frame_cache.h"
0024 #include "kis_update_info.h"
0025 #include "kis_signal_auto_connection.h"
0026 #include "kis_idle_watcher.h"
0027 #include "KisViewManager.h"
0028 #include "kis_node_manager.h"
0029 #include "kis_keyframe_channel.h"
0030 #include "KisMainWindow.h"
0031 
0032 #include <KisLockFrameGenerationLock.h>
0033 #include "KisAsyncAnimationCacheRenderer.h"
0034 #include "dialogs/KisAsyncAnimationCacheRenderDialog.h"
0035 
0036 
0037 struct KisAnimationCachePopulator::Private
0038 {
0039     KisAnimationCachePopulator *q;
0040     KisPart *part;
0041 
0042     QTimer timer;
0043 
0044     /**
0045      * Counts up the number of subsequent times Krita has been detected idle.
0046      */
0047     int idleCounter;
0048     QStack<QPair<KisImageSP, int>> priorityFrames;
0049     static const int IDLE_COUNT_THRESHOLD = 4;
0050     static const int IDLE_CHECK_INTERVAL = 500;
0051     static const int BETWEEN_FRAMES_INTERVAL = 10;
0052 
0053     KisAsyncAnimationCacheRenderer regenerator;
0054     bool calculateAnimationCacheInBackground = true;
0055 
0056     enum State {
0057         NotWaitingForAnything,
0058         WaitingForIdle,
0059         WaitingForFrame,
0060         BetweenFrames
0061     };
0062     State state;
0063 
0064     enum RegenerationRequestResult {
0065         RequestSuccessful = 0,
0066         RequestRejected,
0067         RequestPostponed
0068     };
0069 
0070     Private(KisAnimationCachePopulator *_q, KisPart *_part)
0071         : q(_q),
0072           part(_part),
0073           idleCounter(0),
0074           priorityFrames(),
0075           state(WaitingForIdle)
0076     {
0077         timer.setSingleShot(true);
0078     }
0079 
0080     void timerTimeout() {
0081         switch (state) {
0082         case WaitingForIdle:
0083         case BetweenFrames:
0084             generateIfIdle();
0085             break;
0086         case WaitingForFrame:
0087             KIS_ASSERT_RECOVER_NOOP(0 && "WaitingForFrame cannot have a timeout. Just skip this message and report a bug");
0088             break;
0089         case NotWaitingForAnything:
0090             KIS_ASSERT_RECOVER_NOOP(0 && "NotWaitingForAnything cannot have a timeout. Just skip this message and report a bug");
0091             break;
0092         }
0093     }
0094 
0095     void generateIfIdle()
0096     {
0097         if (part->idleWatcher()->isIdle()) {
0098             idleCounter++;
0099 
0100             if (idleCounter >= IDLE_COUNT_THRESHOLD) {
0101                 RegenerationRequestResult result = tryRequestGeneration();
0102 
0103                 if (result == RequestPostponed) {
0104                     enterState(WaitingForIdle);
0105                 } else if (result == RequestRejected) {
0106                     enterState(NotWaitingForAnything);
0107                 }
0108 
0109                 return;
0110             }
0111         } else {
0112             idleCounter = 0;
0113         }
0114 
0115         enterState(WaitingForIdle);
0116     }
0117 
0118 
0119     RegenerationRequestResult tryRequestGeneration()
0120     {
0121         if (!priorityFrames.isEmpty()) {
0122             KisImageSP image = priorityFrames.top().first;
0123             const int priorityFrame = priorityFrames.top().second;
0124             priorityFrames.pop();
0125 
0126             KisAnimationFrameCacheSP cache = KisAnimationFrameCache::cacheForImage(image);
0127             KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cache, RequestRejected);
0128 
0129             RegenerationRequestResult result =
0130                 tryRequestGeneration(cache, KisTimeSpan(), priorityFrame);
0131             if (result == RequestSuccessful) return result;
0132         }
0133 
0134         // Prioritize the active document
0135         KisAnimationFrameCacheSP activeDocumentCache = KisAnimationFrameCacheSP(0);
0136 
0137         KisMainWindow *activeWindow = part->currentMainwindow();
0138         if (activeWindow && activeWindow->activeView()) {
0139             KisCanvas2 *activeCanvas = activeWindow->activeView()->canvasBase();
0140 
0141             if (activeCanvas && activeCanvas->frameCache()) {
0142                 activeDocumentCache = activeCanvas->frameCache();
0143 
0144                 // Let's skip frames affected by changes to the active node (on the active document)
0145                 // This avoids constant invalidation and regeneration while drawing
0146                 KisNodeSP activeNode = activeCanvas->viewManager()->nodeManager()->activeNode();
0147                 KisTimeSpan skipRange;
0148                 if (activeNode) {
0149                     const int currentTime = activeCanvas->currentImage()->animationInterface()->currentUITime();
0150 
0151                     if (!activeNode->keyframeChannels().isEmpty()) {
0152                         Q_FOREACH (const KisKeyframeChannel *channel, activeNode->keyframeChannels()) {
0153                             skipRange |= channel->affectedFrames(currentTime);
0154                         }
0155                     } else {
0156                         skipRange = KisTimeSpan::infinite(0);
0157                     }
0158                 }
0159 
0160                 RegenerationRequestResult result =
0161                     tryRequestGeneration(activeDocumentCache, skipRange, -1);
0162                 if (result == RequestSuccessful) return result;
0163             }
0164         }
0165 
0166         QList<KisAnimationFrameCache*> caches = KisAnimationFrameCache::caches();
0167         KisAnimationFrameCache *cache;
0168         Q_FOREACH (cache, caches) {
0169             if (cache == activeDocumentCache.data()) {
0170                 // We already handled this one...
0171                 continue;
0172             }
0173 
0174             RegenerationRequestResult result =
0175                 tryRequestGeneration(cache, KisTimeSpan(), -1);
0176             if (result == RequestSuccessful) return result;
0177         }
0178 
0179         return RequestRejected;
0180     }
0181 
0182     RegenerationRequestResult tryRequestGeneration(KisAnimationFrameCacheSP cache, KisTimeSpan skipRange, int priorityFrame)
0183     {
0184         KisImageSP image = cache->image();
0185         if (!image) return RequestRejected;
0186 
0187         KisImageAnimationInterface *animation = image->animationInterface();
0188 
0189         if (animation->backgroundFrameGenerationBlocked()) {
0190             return RequestPostponed;
0191         }
0192 
0193         KisTimeSpan currentRange = animation->documentPlaybackRange();
0194 
0195         const int frame = priorityFrame >= 0 ? priorityFrame : KisAsyncAnimationCacheRenderDialog::calcFirstDirtyFrame(cache, currentRange, skipRange);
0196 
0197         if (frame >= 0) {
0198             return regenerate(cache, frame);
0199         }
0200 
0201         return RequestRejected;
0202     }
0203 
0204     RegenerationRequestResult regenerate(KisAnimationFrameCacheSP cache, int frame)
0205     {
0206         if (state == WaitingForFrame) {
0207             // Already busy, deny request
0208             return RequestRejected;
0209         }
0210 
0211         KisLockFrameGenerationLock lock(cache->image()->animationInterface(), std::try_to_lock);
0212 
0213         if (!lock.owns_lock()) {
0214             return RequestPostponed;
0215         }
0216 
0217         /**
0218          * We should enter the state before the frame is
0219          * requested. Otherwise the signal may come earlier than we
0220          * enter it.
0221          */
0222         enterState(WaitingForFrame);
0223 
0224         regenerator.setFrameCache(cache);
0225 
0226         // if we ever decide to add ROI to background cache
0227         // regeneration, it should be added here :)
0228         regenerator.startFrameRegeneration(cache->image(), frame, KisAsyncAnimationRendererBase::Cancellable, std::move(lock));
0229 
0230         return RequestSuccessful;
0231     }
0232 
0233     QString debugStateToString(State newState) {
0234         QString str = "<unknown>";
0235 
0236         switch (newState) {
0237         case WaitingForIdle:
0238             str = "WaitingForIdle";
0239             break;
0240         case WaitingForFrame:
0241             str = "WaitingForFrame";
0242             break;
0243         case NotWaitingForAnything:
0244             str = "NotWaitingForAnything";
0245             break;
0246         case BetweenFrames:
0247             str = "BetweenFrames";
0248             break;
0249         }
0250 
0251         return str;
0252     }
0253 
0254     void enterState(State newState)
0255     {
0256         //ENTER_FUNCTION() << debugStateToString(state) << "->" << debugStateToString(newState);
0257 
0258         state = newState;
0259         int timerTimeout = -1;
0260 
0261         switch (state) {
0262         case WaitingForIdle:
0263             timerTimeout = IDLE_CHECK_INTERVAL;
0264             break;
0265         case WaitingForFrame:
0266             // the timeout is handled by the regenerator now
0267             timerTimeout = -1;
0268             break;
0269         case NotWaitingForAnything:
0270             // frame conversion cannot be cancelled,
0271             // so there is no timeout
0272             timerTimeout = -1;
0273             break;
0274         case BetweenFrames:
0275             timerTimeout = BETWEEN_FRAMES_INTERVAL;
0276             break;
0277         }
0278 
0279         if (timerTimeout >= 0) {
0280             timer.start(timerTimeout);
0281         } else {
0282             timer.stop();
0283         }
0284     }
0285 };
0286 
0287 KisAnimationCachePopulator::KisAnimationCachePopulator(KisPart *part)
0288     : m_d(new Private(this, part))
0289 {
0290     connect(&m_d->timer, SIGNAL(timeout()), this, SLOT(slotTimer()));
0291 
0292     connect(&m_d->regenerator, SIGNAL(sigFrameCancelled(int, KisAsyncAnimationRendererBase::CancelReason)), SLOT(slotRegeneratorFrameCancelled()));
0293     connect(&m_d->regenerator, SIGNAL(sigFrameCompleted(int)), SLOT(slotRegeneratorFrameReady()));
0294 
0295     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0296     slotConfigChanged();
0297 }
0298 
0299 KisAnimationCachePopulator::~KisAnimationCachePopulator()
0300 {
0301     m_d->priorityFrames.clear();
0302 }
0303 
0304 bool KisAnimationCachePopulator::regenerate(KisAnimationFrameCacheSP cache, int frame)
0305 {
0306     return m_d->regenerate(cache, frame);
0307 }
0308 
0309 void KisAnimationCachePopulator::requestRegenerationWithPriorityFrame(KisImageSP image, int frameIndex)
0310 {
0311     if (!m_d->calculateAnimationCacheInBackground) return;
0312     if (!KisAnimationFrameCache::cacheForImage(image)) return;
0313 
0314     m_d->priorityFrames.append(qMakePair(image, frameIndex));
0315 
0316     if (m_d->state == Private::NotWaitingForAnything) {
0317         m_d->generateIfIdle();
0318     }
0319 }
0320 
0321 void KisAnimationCachePopulator::slotTimer()
0322 {
0323     m_d->timerTimeout();
0324 }
0325 
0326 void KisAnimationCachePopulator::slotRequestRegeneration()
0327 {
0328     // skip if the user forbade background regeneration
0329     if (!m_d->calculateAnimationCacheInBackground) return;
0330 
0331     m_d->enterState(Private::WaitingForIdle);
0332 }
0333 
0334 void KisAnimationCachePopulator::slotRegeneratorFrameCancelled()
0335 {
0336     KIS_ASSERT_RECOVER_RETURN(m_d->state == Private::WaitingForFrame);
0337     m_d->enterState(Private::NotWaitingForAnything);
0338 }
0339 
0340 void KisAnimationCachePopulator::slotRegeneratorFrameReady()
0341 {
0342     m_d->enterState(Private::BetweenFrames);
0343 }
0344 
0345 void KisAnimationCachePopulator::slotConfigChanged()
0346 {
0347     KisConfig cfg(true);
0348     m_d->calculateAnimationCacheInBackground = cfg.calculateAnimationCacheInBackground();
0349     QTimer::singleShot(1000, this, SLOT(slotRequestRegeneration()));
0350 }