10#include <QProgressDialog>
11#include <QElapsedTimer>
12#include <QApplication>
19#include <klocalizedstring.h>
30#include <boost/optional.hpp>
37 std::unique_ptr<KisAsyncAnimationRendererBase> renderer;
42 : renderer(_renderer),
46 RendererPair(RendererPair &&rhs)
47 : renderer(
std::move(rhs.renderer)),
53int calculateNumberMemoryAllowedClones(
KisImageSP image)
62 if (cloneSize > 0 && allowedMemory > 0) {
63 return allowedMemory / cloneSize;
113 : m_d(new
Private(actionTitle, image, busyWait))
115 connect(&
m_d->progressDialogCompressor, SIGNAL(timeout()),
126 KisBlockBackgroundFrameGenerationLock populatorBlock(
m_d->image->animationInterface());
135 bool imageIsIdle =
true;
141 if (
m_d->image->tryBarrierLock(
true)) {
142 m_d->image->unlock();
153 if (!
m_d->isBatchMode) {
156 KisAsyncActionFeedback feedback(i18n(
"Wait for existing frame generation process to complete..."), parentWidget);
161 m_d->framesInProgress.clear();
163 m_d->dirtyFramesCount =
m_d->stillDirtyFrames.size();
165 if (!
m_d->isBatchMode) {
167 m_d->progressDialog.reset(
new QProgressDialog(
m_d->actionTitle, i18n(
"Cancel"), 0, 0, parentWidget));
168 m_d->progressDialog->setWindowModality(Qt::ApplicationModal);
169 m_d->progressDialog->setMinimum(0);
170 m_d->progressDialog->setMaximum(
m_d->dirtyFramesCount);
171 m_d->progressDialog->setMinimumDuration(
m_d->busyWait);
175 if (
m_d->dirtyFramesCount <= 0)
return m_d->result;
177 m_d->processingTime.start();
182 const int numAllowedWorker = 1 + calculateNumberMemoryAllowedClones(
m_d->image);
184 const int numWorkers = qMin(proposedNumWorkers, numAllowedWorker);
185 const int numThreadsPerWorker = qMax(1, qCeil(qreal(maxThreads) / numWorkers));
187 m_d->memoryLimitReached = numWorkers < proposedNumWorkers;
189 const int oldWorkingThreadsLimit =
m_d->image->workingThreadsLimit();
191 for (
int i = 0; i < numWorkers; i++) {
193 const bool lastWorker = (i == numWorkers - 1);
199 if (
m_d->asyncRenderers.size() == 0) {
206 image =
m_d->asyncRenderers[
m_d->asyncRenderers.size() - 1].image->
clone(
true);
217 m_d->asyncRenderers.push_back(RendererPair(renderer, image));
224 if (
m_d->numDirtyFramesLeft() > 0) {
225 m_d->waitLoop.exec();
228 for (
auto &pair :
m_d->asyncRenderers) {
233 pair.image->barrierLock(
true);
234 pair.image->unlock();
238 m_d->asyncRenderers.clear();
243 m_d->image->barrierLock(
true);
244 m_d->image->unlock();
247 m_d->image->setWorkingThreadsLimit(oldWorkingThreadsLimit);
249 m_d->progressDialog.reset();
256 m_d->regionOfInterest = roi;
261 return m_d->regionOfInterest;
268 m_d->framesInProgress.removeOne(frame);
288 for (
auto &pair :
m_d->asyncRenderers) {
289 if (pair.renderer->isActive()) {
290 pair.renderer->cancelCurrentFrameRendering(cancelReason);
295 m_d->stillDirtyFrames.clear();
296 m_d->framesInProgress.clear();
307 bool hadWorkOnPreviousCycle =
false;
309 while (!
m_d->stillDirtyFrames.isEmpty()) {
310 for (
auto &pair :
m_d->asyncRenderers) {
311 if (!pair.renderer->isActive()) {
312 const int currentDirtyFrame =
m_d->stillDirtyFrames.takeFirst();
314 KisLockFrameGenerationLock lock(pair.image->animationInterface());
317 pair.renderer->startFrameRegeneration(pair.image, currentDirtyFrame,
m_d->regionOfInterest,
319 hadWorkOnPreviousCycle =
true;
320 m_d->framesInProgress.append(currentDirtyFrame);
325 if (!hadWorkOnPreviousCycle)
break;
326 hadWorkOnPreviousCycle =
false;
332 const int processedFramesCount =
m_d->dirtyFramesCount -
m_d->numDirtyFramesLeft();
334 const qint64 elapsedMSec =
m_d->processingTime.elapsed();
335 const qint64 estimatedMSec =
336 !processedFramesCount ? 0 :
337 elapsedMSec *
m_d->dirtyFramesCount / processedFramesCount;
339 const QTime elapsedTime = QTime::fromMSecsSinceStartOfDay(elapsedMSec);
340 const QTime estimatedTime = QTime::fromMSecsSinceStartOfDay(estimatedMSec);
342 const QString timeFormat = estimatedTime.hour() > 0 ?
"HH:mm:ss" :
"mm:ss";
344 const QString elapsedTimeString = elapsedTime.toString(timeFormat);
345 const QString estimatedTimeString = estimatedTime.toString(timeFormat);
347 const QString memoryLimitMessage(
348 i18n(
"\n\nThe memory limit has been reached.\nThe number of frames saved simultaneously is limited to %1\n\n",
349 m_d->asyncRenderers.size()));
352 const QString progressLabel(i18n(
"%1\n\nElapsed: %2\nEstimated: %3\n\n%4",
356 m_d->memoryLimitReached ? memoryLimitMessage : QString()));
357 if (
m_d->progressDialog) {
364 m_d->progressDialogCompressor.start();
367 if (!
m_d->numDirtyFramesLeft()) {
368 m_d->waitLoop.quit();
383 if (
m_d->progressDialogReentrancyCounter > 0) {
384 m_d->progressDialogCompressor.start();
388 if (
m_d->progressDialog &&
m_d->progressData) {
389 m_d->progressDialogReentrancyCounter++;
391 m_d->progressDialog->setLabelText(
m_d->progressData->second);
392 m_d->progressDialog->setValue(
m_d->progressData->first);
393 m_d->progressData = boost::none;
395 m_d->progressDialogReentrancyCounter--;
406 return m_d->isBatchMode;
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
void waitForMutex(Mutex &mutex)
void slotFrameCancelled(int frame, KisAsyncAnimationRendererBase::CancelReason cancelReason)
virtual KisAsyncAnimationRendererBase * createRenderer(KisImageSP image)=0
create a renderer object linked to image
virtual Result regenerateRange(KisViewManager *viewManager)
start generation of frames and (if not in batch mode) show the dialog
void setRegionOfInterest(const KisRegion &roi)
const QScopedPointer< Private > m_d
virtual void initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame)=0
void slotUpdateCompressedProgressData()
void updateProgressLabel()
void cancelProcessingImpl(KisAsyncAnimationRendererBase::CancelReason cancelReason)
KisRegion regionOfInterest() const
KisAsyncAnimationRenderDialogBase(const QString &actionTitle, KisImageSP image, int busyWait=200)
construct and initialize the dialog
virtual QList< int > calcDirtyFrames() const =0
returns a list of frames that should be regenerated by the dialog
void slotFrameCompleted(int frame)
virtual ~KisAsyncAnimationRenderDialogBase()
void setBatchMode(bool value)
setting batch mode to true will prevent any dialogs or message boxes from showing on screen....
void slotCancelRegeneration()
void tryInitiateFrameRegeneration()
int frameRenderingClones(bool defaultValue=false) const
int maxNumberOfThreads(bool defaultValue=false) const
void setWorkingThreadsLimit(int value)
void barrierLock(bool readOnly=false)
Wait until all the queued background jobs are completed and lock the image.
KisImage * clone(bool exactCopy=false)
bool blockUntilOperationsFinished(KisImageSP image)
blockUntilOperationsFinished blocks the GUI of the application until execution of actions on image is...
void blockUntilOperationsFinishedForced(KisImageSP image)
blockUntilOperationsFinished blocks the GUI of the application until execution of actions on image is...
QWidget * mainWindowAsQWidget() const
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
QElapsedTimer processingTime
QPair< int, QString > ProgressData
KisSignalCompressor progressDialogCompressor
QList< int > framesInProgress
int numDirtyFramesLeft() const
QScopedPointer< QProgressDialog > progressDialog
int progressDialogReentrancyCounter
QList< int > stillDirtyFrames
std::vector< RendererPair > asyncRenderers
Private(const QString &_actionTitle, KisImageSP _image, int _busyWait)
boost::optional< ProgressData > progressData
KisRegion regionOfInterest
Statistics fetchMemoryStatistics(KisImageSP image) const
static KisMemoryStatisticsServer * instance()