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 }