From cf4969cef6b9263e4df28394508bb56096d3e839 Mon Sep 17 00:00:00 2001 From: Alexandre Gauthier Date: Mon, 1 Feb 2016 14:51:42 +0100 Subject: [PATCH] Stop all threads whenever we stop playback so that when panning/zooming during playback the program does not stall too long --- Engine/OfxHost.cpp | 10 +- Engine/OutputSchedulerThread.cpp | 465 +++++++++++++++++++++++++++---- Engine/OutputSchedulerThread.h | 49 +++- Engine/Settings.cpp | 14 +- Engine/Timer.cpp | 21 +- Engine/Timer.h | 3 +- Gui/PreviewThread.cpp | 7 + 7 files changed, 498 insertions(+), 71 deletions(-) diff --git a/Engine/OfxHost.cpp b/Engine/OfxHost.cpp index 30f423ffc0..3790099a42 100644 --- a/Engine/OfxHost.cpp +++ b/Engine/OfxHost.cpp @@ -92,6 +92,7 @@ CLANG_DIAG_ON(unknown-pragmas) #include "Engine/Node.h" #include "Engine/OfxEffectInstance.h" #include "Engine/OfxImageEffectInstance.h" +#include "Engine/OutputSchedulerThread.h" #include "Engine/OfxMemory.h" #include "Engine/Plugin.h" #include "Engine/Project.h" @@ -99,6 +100,9 @@ CLANG_DIAG_ON(unknown-pragmas) #include "Engine/StandardPaths.h" #include "Engine/TLSHolder.h" +//An effect may not use more than this amount of threads +#define NATRON_MULTI_THREAD_SUITE_MAX_NUM_CPU 4 + NATRON_NAMESPACE_ENTER; // to disambiguate with the global-scope ::OfxHost @@ -1213,7 +1217,9 @@ OfxHost::multiThreadNumCPUS(unsigned int *nCPUs) const int activeThreadsCount = QThreadPool::globalInstance()->activeThreadCount(); // Add the number of threads already running by the multiThreadSuite + parallel renders +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL activeThreadsCount += appPTR->getNRunningThreads(); +#endif // Clamp to 0 activeThreadsCount = std::max( 0, activeThreadsCount); @@ -1231,10 +1237,10 @@ OfxHost::multiThreadNumCPUS(unsigned int *nCPUs) const if (hwConcurrency <= 0) { nThreadsPerEffect = 1; - } else if (hwConcurrency <= 4) { + } else if (hwConcurrency <= NATRON_MULTI_THREAD_SUITE_MAX_NUM_CPU) { nThreadsPerEffect = hwConcurrency; } else { - nThreadsPerEffect = 4; + nThreadsPerEffect = NATRON_MULTI_THREAD_SUITE_MAX_NUM_CPU; } } ///+1 because the current thread is going to wait during the multiThread call so we're better off diff --git a/Engine/OutputSchedulerThread.cpp b/Engine/OutputSchedulerThread.cpp index eb607d2ffa..45e537bd94 100644 --- a/Engine/OutputSchedulerThread.cpp +++ b/Engine/OutputSchedulerThread.cpp @@ -63,6 +63,17 @@ #define NATRON_FPS_REFRESH_RATE_SECONDS 1.5 +/* + When defined, parallel frame renders are spawned from a timer so that the frames + appear to be rendered all at the same speed. + When undefined each time a frame is computed a new thread will be spawned + until we reach the maximum allowed parallel frame renders. + */ +//#define NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + +#ifdef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER +#define NATRON_SCHEDULER_THREADS_SPAWN_DEFAULT_TIMEOUT_MS 500 +#endif NATRON_NAMESPACE_ENTER; @@ -154,10 +165,12 @@ struct ProducedFrame bool isAborted; }; +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL static bool isBufferFull(int nbBufferedElement, int hardwardIdealThreadCount) { return nbBufferedElement >= hardwardIdealThreadCount * 3; } +#endif struct OutputSchedulerThreadPrivate { @@ -178,9 +191,10 @@ struct OutputSchedulerThreadPrivate QWaitCondition startRequestsCond; QMutex startRequestsMutex; - int abortRequested; // true when the user wants to stop the engine, e.g: the user disconnected the viewer - bool isAbortRequestBlocking; + int abortRequested; // true when the user wants to stop the engine, e.g: the user disconnected the viewer, protected by abortedRequestedMutex + bool isAbortRequestBlocking; // protected by abortedRequestedMutex + //If true then a current frame render can start playback, protected by abortedRequestedMutex bool canAutoRestartPlayback; QWaitCondition abortedRequestedCondition; @@ -223,23 +237,40 @@ struct OutputSchedulerThreadPrivate mutable QMutex renderThreadsMutex; RenderThreads renderThreads; QWaitCondition allRenderThreadsInactiveCond; // wait condition to make sure all render threads are asleep + +#ifdef NATRON_PLAYBACK_USES_THREAD_POOL + QThreadPool* threadPool; + +#else + QWaitCondition allRenderThreadsQuitCond; //to make sure all render threads have quit + + + std::list framesToRender; + + ///Render threads wait in this condition and the scheduler wake them when it needs to render some frames + QWaitCondition framesToRenderNotEmptyCond; + +#endif ///Work queue filled by the scheduler thread when in playback/render on disk QMutex framesToRenderMutex; // protects framesToRender & currentFrameRequests - std::list framesToRender; ///index of the last frame pushed (framesToRender.back()) ///we store this because when we call pushFramesToRender we need to know what was the last frame that was queued ///Protected by framesToRenderMutex int lastFramePushedIndex; - - ///Render threads wait in this condition and the scheduler wake them when it needs to render some frames - QWaitCondition framesToRenderNotEmptyCond; - - + boost::weak_ptr outputEffect; //< The effect used as output device RenderEngine* engine; + +#ifdef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + QTimer threadSpawnsTimer; + + QMutex lastRecordedFPSMutex; + double lastRecordedFPS; + +#endif OutputSchedulerThreadPrivate(RenderEngine* engine,const boost::shared_ptr& effect,OutputSchedulerThread::ProcessFrameModeEnum mode) @@ -277,15 +308,24 @@ struct OutputSchedulerThreadPrivate , renderThreadsMutex() , renderThreads() , allRenderThreadsInactiveCond() +#ifdef NATRON_PLAYBACK_USES_THREAD_POOL + , threadPool(QThreadPool::globalInstance()) +#else , allRenderThreadsQuitCond() - , framesToRenderMutex() , framesToRender() - , lastFramePushedIndex(0) , framesToRenderNotEmptyCond() +#endif + , framesToRenderMutex() + , lastFramePushedIndex(0) , outputEffect(effect) , engine(engine) +#ifdef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + , threadSpawnsTimer() + , lastRecordedFPSMutex() + , lastRecordedFPS(0.) +#endif { - + } bool appendBufferedFrame(double time, @@ -328,11 +368,16 @@ struct OutputSchedulerThreadPrivate void appendRunnable(RenderThreadTask* runnable) { + assert(!renderThreadsMutex.tryLock()); RenderThread r; r.thread = runnable; r.active = true; renderThreads.push_back(r); +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL runnable->start(); +#else + threadPool->start(runnable); +#endif } @@ -387,14 +432,24 @@ struct OutputSchedulerThreadPrivate void waitForRenderThreadsToBeDone() { assert( !renderThreadsMutex.tryLock() ); - while (renderThreads.size() > 0 && getNActiveRenderThreads() > 0) { + while (renderThreads.size() > 0 +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL + /* + When not using the thread pool we use the same threads for computing several frames. + When using the thread-pool tasks are actually just removed from renderThreads when they are finisehd + */ + && getNActiveRenderThreads() > 0 +#endif + ) { allRenderThreadsInactiveCond.wait(&renderThreadsMutex); } } int getNActiveRenderThreads() const { ///Private shouldn't lock - assert( !renderThreadsMutex.tryLock() ); + assert(!renderThreadsMutex.tryLock()); + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL int ret = 0; for (RenderThreads::const_iterator it = renderThreads.begin(); it != renderThreads.end(); ++it) { if (it->active) { @@ -402,8 +457,16 @@ struct OutputSchedulerThreadPrivate } } return ret; +#else + /* + When not using the thread pool we use the same threads for computing several frames. + When using the thread-pool tasks are actually just removed from renderThreads when they are finisehd + */ + return (int)renderThreads.size(); +#endif } +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL void removeQuitRenderThreadsInternal() { for (;;) { @@ -422,7 +485,9 @@ struct OutputSchedulerThreadPrivate } } } - +#endif + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL void removeAllQuitRenderThreads() { ///Private shouldn't lock assert(!renderThreadsMutex.tryLock()); @@ -432,9 +497,11 @@ struct OutputSchedulerThreadPrivate ///Wake-up the main-thread if it was waiting for all threads to quit allRenderThreadsQuitCond.wakeOne(); } +#endif void waitForRenderThreadsToQuit() { +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL RenderThreads threads; { QMutexLocker l(&renderThreadsMutex); @@ -450,6 +517,13 @@ struct OutputSchedulerThreadPrivate removeQuitRenderThreadsInternal(); assert(renderThreads.empty()); } +#else + /* + We don't need the threads to actually quit, just need the runnables to be done + */ + QMutexLocker l(&renderThreadsMutex); + waitForRenderThreadsToBeDone(); +#endif } @@ -460,14 +534,18 @@ OutputSchedulerThread::OutputSchedulerThread(RenderEngine* engine,const boost::s : QThread() , _imp(new OutputSchedulerThreadPrivate(engine,effect,mode)) { - QObject::connect(this, SIGNAL(s_doProcessOnMainThread(BufferedFrames,bool,int)), this, - SLOT(doProcessFrameMainThread(BufferedFrames,bool,int))); + QObject::connect(this, SIGNAL(s_doProcessOnMainThread(BufferedFrames)), this, + SLOT(doProcessFrameMainThread(BufferedFrames))); QObject::connect(_imp->timer.get(), SIGNAL(fpsChanged(double,double)), _imp->engine, SIGNAL(fpsChanged(double,double))); QObject::connect(this, SIGNAL(s_abortRenderingOnMainThread(bool,bool)), this, SLOT(abortRendering(bool,bool))); +#ifdef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + QObject::connect(&_imp->threadSpawnsTimer, SIGNAL(timeout()), this, SLOT(onThreadSpawnsTimerTriggered())); +#endif + setObjectName("Scheduler thread"); } @@ -586,6 +664,7 @@ OutputSchedulerThreadPrivate::getNearestInSequence(OutputSchedulerThread::Render } +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL void OutputSchedulerThread::pushFramesToRender(int startingFrame,int nThreads) { @@ -625,7 +704,6 @@ OutputSchedulerThread::pushFramesToRenderInternal(int startingFrame,int nThreads ///Push 2x the count of threads to be sure no one will be waiting while ((int)_imp->framesToRender.size() < nThreads * 2) { _imp->framesToRender.push_back(startingFrame); - _imp->lastFramePushedIndex = startingFrame; if (!OutputSchedulerThreadPrivate::getNextFrameInSequence(pMode, direction, startingFrame, @@ -701,6 +779,7 @@ OutputSchedulerThread::pushFramesToRender(int nThreads) } } + int OutputSchedulerThread::pickFrameToRender(RenderThreadTask* thread,bool* enableRenderStats, std::vector* viewsToRender) { @@ -774,6 +853,140 @@ OutputSchedulerThread::pickFrameToRender(RenderThreadTask* thread,bool* enableRe return -1; } +#else // NATRON_PLAYBACK_USES_THREAD_POOL + +void +OutputSchedulerThread::startTasksFromLastStartedFrame() +{ + int frame; + bool canContinue; + + { + QMutexLocker l(&_imp->framesToRenderMutex); + + RenderDirectionEnum direction; + int firstFrame,lastFrame,frameStep; + { + QMutexLocker l(&_imp->runArgsMutex); + direction = _imp->livingRunArgs.timelineDirection; + firstFrame = _imp->livingRunArgs.firstFrame; + lastFrame = _imp->livingRunArgs.lastFrame; + frameStep = _imp->livingRunArgs.frameStep; + } + + PlaybackModeEnum pMode = _imp->engine->getPlaybackMode(); + + frame = _imp->lastFramePushedIndex; + if (firstFrame == lastFrame && frame == firstFrame) { + return; + } + + ///If startingTime is already taken into account in the framesToRender, push new frames from the last one in the stack instead + canContinue = OutputSchedulerThreadPrivate::getNextFrameInSequence(pMode, direction, frame, + firstFrame, lastFrame, frameStep, &frame, &direction); + } + if (canContinue) { + QMutexLocker l(&_imp->renderThreadsMutex); + startTasks(frame); + } + +} + +void +OutputSchedulerThread::startTasks(int startingFrame) +{ + + int maxThreads = _imp->threadPool->maxThreadCount(); + int activeThreads = _imp->getNActiveRenderThreads(); + + //This thread is from the thread pool so do not count it as it is probably done anyway + if (QThread::currentThread() != this) { + activeThreads -= 1; + } + + + + int nFrames; +#ifdef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + //We check every now and then if we need to start new threads + { + int nbAvailableThreads = maxThreads - activeThreads; + if (nbAvailableThreads <= 0) { + return; + } + nFrames = 1; + } +#else + //Start one more thread until we use all the thread pool. + //We leave some CPU available so that the multi-thread suite can take advantage of it + nFrames = std::max(std::min(maxThreads - activeThreads, 2), 1); +#endif + + + RenderDirectionEnum direction; + int firstFrame,lastFrame,frameStep; + bool useStats; + std::vector viewsToRender; + { + QMutexLocker l(&_imp->runArgsMutex); + direction = _imp->livingRunArgs.timelineDirection; + firstFrame = _imp->livingRunArgs.firstFrame; + lastFrame = _imp->livingRunArgs.lastFrame; + frameStep = _imp->livingRunArgs.frameStep; + useStats = _imp->livingRunArgs.enableRenderStats; + viewsToRender = _imp->livingRunArgs.viewsToRender; + } + + PlaybackModeEnum pMode = _imp->engine->getPlaybackMode(); + if (firstFrame == lastFrame) { + RenderThreadTask* task = createRunnable(startingFrame, useStats, viewsToRender); + _imp->appendRunnable(task); + + QMutexLocker k(&_imp->framesToRenderMutex); + _imp->lastFramePushedIndex = startingFrame; + } else { + int frame = startingFrame; + for (int i = 0; i < nFrames; ++i) { + RenderThreadTask* task = createRunnable(frame, useStats, viewsToRender); + _imp->appendRunnable(task); + + + { + QMutexLocker k(&_imp->framesToRenderMutex); + _imp->lastFramePushedIndex = frame; + } + + if (!OutputSchedulerThreadPrivate::getNextFrameInSequence(pMode, direction, frame, + firstFrame, lastFrame, frameStep, &frame, &direction)) { + break; + } + } + } +} + +#endif //NATRON_PLAYBACK_USES_THREAD_POOL + + +void +OutputSchedulerThread::onThreadSpawnsTimerTriggered() +{ +#ifdef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + +#ifdef NATRON_PLAYBACK_USES_THREAD_POOL + startTasksFromLastStartedFrame(); +#else + /////////// + /////If we were analysing the CPU activity, now set the appropriate number of threads to render. + int newNThreads, lastNThreads; + adjustNumberOfThreads(&newNThreads,&lastNThreads); + + /////////// + /////Append render requests for the render threads + pushFramesToRender(newNThreads); +#endif + +#endif +} void OutputSchedulerThread::notifyThreadAboutToQuit(RenderThreadTask* thread) @@ -782,8 +995,14 @@ OutputSchedulerThread::notifyThreadAboutToQuit(RenderThreadTask* thread) RenderThreads::iterator found = _imp->getRunnableIterator(thread); if (found != _imp->renderThreads.end()) { found->active = false; +#ifdef NATRON_PLAYBACK_USES_THREAD_POOL + _imp->renderThreads.erase(found); +#endif _imp->allRenderThreadsInactiveCond.wakeOne(); + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL _imp->allRenderThreadsQuitCond.wakeOne(); +#endif } } @@ -794,10 +1013,13 @@ OutputSchedulerThread::isBeingAborted() const return _imp->abortFlag; } + void OutputSchedulerThread::startRender() { + + if ( isFPSRegulationNeeded() ) { _imp->timer->playState = ePlayStateRunning; } @@ -828,19 +1050,21 @@ OutputSchedulerThread::startRender() QMutexLocker l(&_imp->workingMutex); _imp->working = true; } - + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL int nThreads; { QMutexLocker l(&_imp->renderThreadsMutex); _imp->removeAllQuitRenderThreads(); nThreads = (int)_imp->renderThreads.size(); } - + ///Start with one thread if it doesn't exist if (nThreads == 0) { int lastNThreads; adjustNumberOfThreads(&nThreads, &lastNThreads); } +#endif QMutexLocker l(&_imp->renderThreadsMutex); @@ -848,9 +1072,10 @@ OutputSchedulerThread::startRender() SchedulingPolicyEnum policy = getSchedulingPolicy(); if (policy == eSchedulingPolicyFFA) { - +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL ///push all frame range and let the threads deal with it pushAllFrameRange(); +#endif } else { ///If the output effect is sequential (only WriteFFMPEG for now) @@ -871,12 +1096,23 @@ OutputSchedulerThread::startRender() return; } } +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL ///Push as many frames as there are threads pushFramesToRender(startingFrame,nThreads); +#endif } - +#ifdef NATRON_PLAYBACK_USES_THREAD_POOL + startTasks(startingFrame); +#endif + +#ifdef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + QMutexLocker k(&_imp->lastRecordedFPSMutex); + double timeoutMS = _imp->lastRecordedFPS == 0. ? NATRON_SCHEDULER_THREADS_SPAWN_DEFAULT_TIMEOUT_MS : (1. / _imp->lastRecordedFPS) * 1000; + _imp->threadSpawnsTimer.start(timeoutMS); +#endif + } void @@ -884,19 +1120,35 @@ OutputSchedulerThread::stopRender() { _imp->timer->playState = ePlayStatePause; +#ifdef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + QMutexLocker k(&_imp->lastRecordedFPSMutex); + _imp->lastRecordedFPS = _imp->timer->getActualFrameRate(); + _imp->threadSpawnsTimer.stop(); +#endif + ///Wait for all render threads to be done + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL ///Clear the work queue { QMutexLocker framesLocker (&_imp->framesToRenderMutex); _imp->framesToRender.clear(); } - - { +#endif + + ///Remove all current threads so the new render doesn't have many threads concurrently trying to do the same thing at the same time +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL + stopRenderThreads(0); +#endif + _imp->waitForRenderThreadsToQuit(); + + + /*{ QMutexLocker l(&_imp->renderThreadsMutex); _imp->removeAllQuitRenderThreads(); _imp->waitForRenderThreadsToBeDone(); - } + }*/ ///If the output effect is sequential (only WriteFFMPEG for now) @@ -1088,6 +1340,9 @@ OutputSchedulerThread::run() } if (!renderFinished) { +#ifndef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL /////////// /////If we were analysing the CPU activity, now set the appropriate number of threads to render. int newNThreads, lastNThreads; @@ -1096,6 +1351,11 @@ OutputSchedulerThread::run() /////////// /////Append render requests for the render threads pushFramesToRender(newNThreads); +#else + startTasksFromLastStartedFrame(); +#endif + +#endif } } // if (!renderFinished) { @@ -1114,17 +1374,6 @@ OutputSchedulerThread::run() if (_imp->mode == eProcessFrameBySchedulerThread) { processFrame(framesToRender); - - if (!renderFinished) { - ///Timeline might have changed if another thread moved the playhead - int timelineCurrentTime = timelineGetTime(); - if (timelineCurrentTime != expectedTimeToRender) { - timelineGoTo(timelineCurrentTime); - } else { - timelineGoTo(nextFrameToRender); - } - - } } else { ///Process on main-thread @@ -1143,26 +1392,27 @@ OutputSchedulerThread::run() } _imp->processRunning = true; - - int timeToSeek = 0; - if (!renderFinished) { - ///Timeline might have changed if another thread moved the playhead - int timelineCurrentTime = timelineGetTime(); - if (timelineCurrentTime != expectedTimeToRender) { - timeToSeek = timelineCurrentTime; - } else { - timeToSeek = nextFrameToRender; - } - - } + - Q_EMIT s_doProcessOnMainThread(framesToRender,!renderFinished, timeToSeek); + Q_EMIT s_doProcessOnMainThread(framesToRender); while (_imp->processRunning) { _imp->processCondition.wait(&_imp->processMutex); } + + } // if (_imp->mode == eProcessFrameBySchedulerThread) { + if (!renderFinished) { + ///Timeline might have changed if another thread moved the playhead + int timelineCurrentTime = timelineGetTime(); + if (timelineCurrentTime != expectedTimeToRender) { + timelineGoTo(timelineCurrentTime); + } else { + timelineGoTo(nextFrameToRender); + } + + } //////////// /////At this point the frame has been processed by the output device @@ -1223,6 +1473,7 @@ OutputSchedulerThread::run() } +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL void OutputSchedulerThread::adjustNumberOfThreads(int* newNThreads, int *lastNThreads) { @@ -1271,6 +1522,7 @@ OutputSchedulerThread::adjustNumberOfThreads(int* newNThreads, int *lastNThreads *newNThreads = std::max(1,currentParallelRenders); } } +#endif void OutputSchedulerThread::notifyFrameRendered(int frame, @@ -1322,10 +1574,18 @@ OutputSchedulerThread::notifyFrameRendered(int frame, } else { l.unlock(); +#ifndef NATRON_SCHEDULER_SPAWN_THREADS_WITH_TIMER + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL /////////// /////If we were analysing the CPU activity, now set the appropriate number of threads to render. int newNThreads; adjustNumberOfThreads(&newNThreads, &nbCurParallelRenders); +#else + startTasksFromLastStartedFrame(); +#endif + +#endif } } else { { @@ -1499,7 +1759,7 @@ OutputSchedulerThread::appendToBuffer(double time, void -OutputSchedulerThread::doProcessFrameMainThread(const BufferedFrames& frames,bool mustSeekTimeline,int time) +OutputSchedulerThread::doProcessFrameMainThread(const BufferedFrames& frames) { assert(QThread::currentThread() == qApp->thread()); { @@ -1512,10 +1772,7 @@ OutputSchedulerThread::doProcessFrameMainThread(const BufferedFrames& frames,boo processFrame(frames); - - if (mustSeekTimeline) { - timelineGoTo(time); - } + QMutexLocker processLocker (&_imp->processMutex); _imp->processRunning = false; @@ -1837,7 +2094,7 @@ void OutputSchedulerThread::stopRenderThreads(int nThreadsToStop) { - +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL { ///First flag the number of threads to stop @@ -1856,13 +2113,20 @@ OutputSchedulerThread::stopRenderThreads(int nThreadsToStop) _imp->removeAllQuitRenderThreads(); } - ///Wake-up all threads to make sure that they are notified that they must quit { QMutexLocker framesLocker(&_imp->framesToRenderMutex); _imp->framesToRenderNotEmptyCond.wakeAll(); } +#else + Q_UNUSED(nThreadsToStop); + QMutexLocker l(&_imp->renderThreadsMutex); + _imp->waitForRenderThreadsToBeDone(); +#endif + + + } @@ -1909,34 +2173,66 @@ struct RenderThreadTaskPrivate boost::weak_ptr output; +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL QMutex mustQuitMutex; bool mustQuit; bool hasQuit; QMutex runningMutex; bool running; - - RenderThreadTaskPrivate(const boost::shared_ptr& output,OutputSchedulerThread* scheduler) +#else + int time; + bool useRenderStats; + std::vector viewsToRender; +#endif + + RenderThreadTaskPrivate(const boost::shared_ptr& output, + OutputSchedulerThread* scheduler + #ifdef NATRON_PLAYBACK_USES_THREAD_POOL + , + const int time, + const bool useRenderStats, + const std::vector& viewsToRender + #endif + ) : scheduler(scheduler) , output(output) +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL , mustQuitMutex() , mustQuit(false) , hasQuit(false) , runningMutex() , running(false) +#else + , time(time) + , useRenderStats(useRenderStats) + , viewsToRender(viewsToRender) +#endif { } }; +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL RenderThreadTask::RenderThreadTask(const boost::shared_ptr& output,OutputSchedulerThread* scheduler) : QThread() , _imp(new RenderThreadTaskPrivate(output,scheduler)) { setObjectName("Parallel render thread"); } - +#else +RenderThreadTask::RenderThreadTask(const boost::shared_ptr& output, + OutputSchedulerThread* scheduler, + const int time, + const bool useRenderStats, + const std::vector& viewsToRender) +: QRunnable() +, _imp(new RenderThreadTaskPrivate(output, scheduler, time, useRenderStats, viewsToRender)) +{ + +} +#endif RenderThreadTask::~RenderThreadTask() { @@ -1946,6 +2242,7 @@ void RenderThreadTask::run() { +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL notifyIsRunning(true); for (;;) { @@ -1973,9 +2270,14 @@ RenderThreadTask::run() } notifyIsRunning(false); _imp->scheduler->notifyThreadAboutToQuit(this); +#else // NATRON_PLAYBACK_USES_THREAD_POOL + renderFrame(_imp->time, _imp->viewsToRender, _imp->useRenderStats); + _imp->scheduler->notifyThreadAboutToQuit(this); +#endif } +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL bool RenderThreadTask::hasQuit() const { @@ -2012,6 +2314,7 @@ RenderThreadTask::notifyIsRunning(bool running) appPTR->fetchAndAddNRunningThreads(running ? 1 : - 1); } +#endif //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// @@ -2035,11 +2338,25 @@ class DefaultRenderFrameRunnable : public RenderThreadTask public: + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL DefaultRenderFrameRunnable(const boost::shared_ptr& writer,OutputSchedulerThread* scheduler) : RenderThreadTask(writer,scheduler) { } +#else + DefaultRenderFrameRunnable(const boost::shared_ptr& writer, + OutputSchedulerThread* scheduler, + const int time, + const bool useRenderStats, + const std::vector& viewsToRender) + : RenderThreadTask(writer,scheduler, time, useRenderStats, viewsToRender) + { + + } +#endif + virtual ~DefaultRenderFrameRunnable() { @@ -2241,11 +2558,19 @@ class DefaultRenderFrameRunnable : public RenderThreadTask } }; +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL RenderThreadTask* DefaultScheduler::createRunnable() { return new DefaultRenderFrameRunnable(_effect.lock(),this); } +#else +RenderThreadTask* +DefaultScheduler::createRunnable(int frame, bool useRenderStarts, const std::vector& viewsToRender) +{ + return new DefaultRenderFrameRunnable(_effect.lock(),this, frame, useRenderStarts, viewsToRender); +} +#endif @@ -2601,12 +2926,26 @@ class ViewerRenderFrameRunnable : public RenderThreadTask public: +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL ViewerRenderFrameRunnable(const boost::shared_ptr& viewer,OutputSchedulerThread* scheduler) : RenderThreadTask(viewer,scheduler) , _viewer(viewer) { } +#else + ViewerRenderFrameRunnable(const boost::shared_ptr& viewer, + OutputSchedulerThread* scheduler, + const int frame, + const bool useRenderStarts, + const std::vector& viewsToRender) + : RenderThreadTask(viewer,scheduler, frame, useRenderStarts, viewsToRender) + , _viewer(viewer) + { + + } +#endif + virtual ~ViewerRenderFrameRunnable() { @@ -2707,11 +3046,21 @@ class ViewerRenderFrameRunnable : public RenderThreadTask } }; + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL RenderThreadTask* ViewerDisplayScheduler::createRunnable() { return new ViewerRenderFrameRunnable(_viewer.lock(),this); } +#else +RenderThreadTask* +ViewerDisplayScheduler::createRunnable(int frame, bool useRenderStarts, const std::vector& viewsToRender) +{ + return new ViewerRenderFrameRunnable(_viewer.lock(),this, frame, useRenderStarts, viewsToRender); +} +#endif + void ViewerDisplayScheduler::handleRenderFailure(const std::string& /*errorMessage*/) diff --git a/Engine/OutputSchedulerThread.h b/Engine/OutputSchedulerThread.h index a3b125fe90..98bc74347b 100644 --- a/Engine/OutputSchedulerThread.h +++ b/Engine/OutputSchedulerThread.h @@ -34,6 +34,9 @@ #include "Global/GlobalDefines.h" #include "Engine/EngineFwd.h" +//#define NATRON_PLAYBACK_USES_THREAD_POOL + + NATRON_NAMESPACE_ENTER; typedef boost::shared_ptr RenderStatsPtr; @@ -89,18 +92,32 @@ typedef std::list BufferedFrames; class OutputSchedulerThread; struct RenderThreadTaskPrivate; + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL class RenderThreadTask : public QThread +#else +class RenderThreadTask : public QRunnable +#endif { public: - + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL RenderThreadTask(const boost::shared_ptr& output,OutputSchedulerThread* scheduler); +#else + RenderThreadTask(const boost::shared_ptr& output, + OutputSchedulerThread* scheduler, + const int time, + const bool useRenderStats, + const std::vector& viewsToRender); +#endif virtual ~RenderThreadTask(); virtual void run() OVERRIDE FINAL; +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL /** * @brief Call this to quit the thread whenever it will return to the pickFrameToRender function **/ @@ -111,6 +128,7 @@ class RenderThreadTask : public QThread bool hasQuit() const; void notifyIsRunning(bool running); +#endif protected: @@ -268,11 +286,12 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON **/ int getNActiveRenderThreads() const; +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL /** * @brief Called by render-threads to pick some work to do or to get asleep if theres nothing to do **/ int pickFrameToRender(RenderThreadTask* thread, bool* enableRenderStats, std::vector* viewsToRender); - +#endif /** * @brief Called by the render-threads when mustQuit() is true on the thread @@ -299,7 +318,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON public Q_SLOTS: - void doProcessFrameMainThread(const BufferedFrames& frames,bool mustSeekTimeline,int time); + void doProcessFrameMainThread(const BufferedFrames& frames); /** @brief Aborts all computations. This turns on the flag abortRequested and will inform the engine that it needs to stop. @@ -317,10 +336,13 @@ public Q_SLOTS: **/ void abortRendering(bool autoRestart, bool blocking); +private Q_SLOTS: + + void onThreadSpawnsTimerTriggered(); Q_SIGNALS: - void s_doProcessOnMainThread(const BufferedFrames& frames,bool mustSeekTimeline,int time); + void s_doProcessOnMainThread(const BufferedFrames& frames); void s_abortRenderingOnMainThread(bool userRequested,bool blocking); @@ -378,10 +400,12 @@ public Q_SLOTS: * @brief Must create a runnable task that will render 1 frame in a separate thread. * The internal thread pool will take care of the thread * The task will pick frames to render until there are no more to be rendered. - * @param playbackOrRender Used as a hint to know that we're rendering for playback or render on disk - * and not just for one frame **/ +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL virtual RenderThreadTask* createRunnable() = 0; +#else + virtual RenderThreadTask* createRunnable(int frame, bool useRenderStarts, const std::vector& viewsToRender) = 0; +#endif /** * @brief Called upon failure of a thread to render an image @@ -417,6 +441,7 @@ public Q_SLOTS: virtual void run() OVERRIDE FINAL; +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL /** * @brief Called by the scheduler threads to wake-up render threads and make them do some work * It calls pushFramesToRenderInternal. It starts pushing frames from lastFramePushedIndex @@ -438,6 +463,10 @@ public Q_SLOTS: * @param optimalNThreads[out] Will be set to the new number of threads **/ void adjustNumberOfThreads(int* newNThreads, int *lastNThreads); +#else + void startTasksFromLastStartedFrame(); + void startTasks(int startingFrame); +#endif /** * @brief Make nThreadsToStop quit running. If 0 then all threads will be destroyed. @@ -476,7 +505,11 @@ class DefaultScheduler : public OutputSchedulerThread virtual int timelineGetTime() const OVERRIDE FINAL WARN_UNUSED_RETURN; +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL virtual RenderThreadTask* createRunnable() OVERRIDE FINAL WARN_UNUSED_RETURN; +#else + virtual RenderThreadTask* createRunnable(int frame, bool useRenderStarts, const std::vector& viewsToRender) OVERRIDE FINAL WARN_UNUSED_RETURN; +#endif virtual void handleRenderFailure(const std::string& errorMessage) OVERRIDE FINAL; @@ -517,7 +550,11 @@ class ViewerDisplayScheduler : public OutputSchedulerThread virtual void getFrameRangeToRender(int& first,int& last) const OVERRIDE FINAL; +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL virtual RenderThreadTask* createRunnable() OVERRIDE FINAL WARN_UNUSED_RETURN; +#else + virtual RenderThreadTask* createRunnable(int frame, bool useRenderStarts, const std::vector& viewsToRender) OVERRIDE FINAL WARN_UNUSED_RETURN; +#endif virtual void handleRenderFailure(const std::string& errorMessage) OVERRIDE FINAL; diff --git a/Engine/Settings.cpp b/Engine/Settings.cpp index f9104597b0..d4e09f8605 100644 --- a/Engine/Settings.cpp +++ b/Engine/Settings.cpp @@ -46,6 +46,7 @@ #include "Engine/KnobFile.h" #include "Engine/KnobTypes.h" #include "Engine/LibraryBinary.h" +#include "Engine/OutputSchedulerThread.h" #include "Engine/Node.h" #include "Engine/Plugin.h" #include "Engine/Project.h" @@ -213,6 +214,7 @@ Settings::initializeKnobsGeneral() _numberOfThreads->setDisplayMinimum(-1); _generalTab->addKnob(_numberOfThreads); +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL _numberOfParallelRenders = AppManager::createKnob(this, "Number of parallel renders (0=\"guess\")"); _numberOfParallelRenders->setHintToolTip("Controls the number of parallel frame that will be rendered at the same time by the renderer." "A value of 0 indicate that " NATRON_APPLICATION_NAME " should automatically determine " @@ -225,6 +227,7 @@ Settings::initializeKnobsGeneral() _numberOfParallelRenders->disableSlider(); _numberOfParallelRenders->setAnimationEnabled(false); _generalTab->addKnob(_numberOfParallelRenders); +#endif _useThreadPool = AppManager::createKnob(this, "Effects use thread-pool"); _useThreadPool->setName("useThreadPool"); @@ -1376,7 +1379,11 @@ Settings::setDefaultValues() _loadProjectsWorkspace->setDefaultValue(false); _useNodeGraphHints->setDefaultValue(true); _numberOfThreads->setDefaultValue(0,0); + +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL _numberOfParallelRenders->setDefaultValue(0,0); +#endif + _useThreadPool->setDefaultValue(true); _nThreadsPerEffect->setDefaultValue(0); _renderInSeparateProcess->setDefaultValue(false,0); @@ -2945,14 +2952,19 @@ Settings::getCheckerboardColor2(double* r,double* g,double* b,double* a) const int Settings::getNumberOfParallelRenders() const { +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL return _numberOfParallelRenders->getValue(); - +#else + return 1; +#endif } void Settings::setNumberOfParallelRenders(int nb) { +#ifndef NATRON_PLAYBACK_USES_THREAD_POOL _numberOfParallelRenders->setValue(nb, 0); +#endif } bool diff --git a/Engine/Timer.cpp b/Engine/Timer.cpp index a3b9000064..48610b4cc9 100644 --- a/Engine/Timer.cpp +++ b/Engine/Timer.cpp @@ -216,10 +216,18 @@ Timer::waitUntilNextFrameIsDue () if (t > NATRON_FPS_REFRESH_RATE_SECONDS) { double actualFrameRate = _framesSinceLastFpsFrame / t; - if (actualFrameRate != _actualFrameRate) { - _actualFrameRate = actualFrameRate; - Q_EMIT fpsChanged(_actualFrameRate,getDesiredFrameRate()); + double curActualFrameRate; + { + QMutexLocker l(_mutex); + if (actualFrameRate != _actualFrameRate) { + _actualFrameRate = actualFrameRate; + } + curActualFrameRate = _actualFrameRate; } + + + Q_EMIT fpsChanged(curActualFrameRate,getDesiredFrameRate()); + _framesSinceLastFpsFrame = 0; } @@ -231,6 +239,13 @@ Timer::waitUntilNextFrameIsDue () _framesSinceLastFpsFrame += 1; } // waitUntilNextFrameIsDue +double +Timer::getActualFrameRate() const +{ + QMutexLocker l(_mutex); + return _actualFrameRate; +} + void Timer::setDesiredFrameRate (double fps) { diff --git a/Engine/Timer.h b/Engine/Timer.h index 7b90352373..b64cce1626 100644 --- a/Engine/Timer.h +++ b/Engine/Timer.h @@ -94,6 +94,7 @@ class Timer : public QObject void setDesiredFrameRate (double fps); double getDesiredFrameRate() const; + double getActualFrameRate() const; //------------------- // Current play state @@ -118,7 +119,7 @@ class Timer : public QObject int _framesSinceLastFpsFrame; // actual frame rate, averaged double _actualFrameRate; // over several frames - QMutex* _mutex; //< protects _spf which is the only member that can + QMutex* _mutex; //< protects _spf and _actualFrameRate }; diff --git a/Gui/PreviewThread.cpp b/Gui/PreviewThread.cpp index a244992699..4c2a599de1 100644 --- a/Gui/PreviewThread.cpp +++ b/Gui/PreviewThread.cpp @@ -166,6 +166,10 @@ PreviewThread::run() if (front.node) { + + ///Mark this thread as running + appPTR->fetchAndAddNRunningThreads(1); + //process the request if valid int w = NATRON_PREVIEW_WIDTH; int h = NATRON_PREVIEW_HEIGHT; @@ -185,6 +189,9 @@ PreviewThread::run() front.node->copyPreviewImageBuffer(_imp->data, w, h); } } + + ///Unmark this thread as running + appPTR->fetchAndAddNRunningThreads(-1); }