File indexing completed on 2024-05-12 16:01:25

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