From 89ae6e026f76627d834cd53a51a8f022369a715f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 24 May 2020 15:20:35 +1000 Subject: [PATCH] cache: reimplemented auto-caching Implemented a smarter auto-cache that's context sensitive. Caching will automatically pause when playback begins or values are being changed and resume when inactive. --- app/config/config.cpp | 3 + app/core.cpp | 4 ++ app/panel/taskmanager/taskmanager.cpp | 1 + app/render/backend/renderbackend.cpp | 4 ++ app/render/framehashcache.cpp | 5 ++ app/render/framehashcache.h | 1 + app/task/cache/cache.cpp | 4 +- app/task/cache/cache.h | 2 + app/task/render/render.cpp | 14 ++-- app/task/render/render.h | 4 ++ app/task/taskmanager.cpp | 22 ++++++ app/task/taskmanager.h | 4 ++ app/widget/taskview/taskview.cpp | 1 + app/widget/taskview/taskview.h | 3 + app/widget/taskview/taskviewitem.cpp | 5 +- app/widget/taskview/taskviewitem.h | 3 + app/widget/viewer/viewer.cpp | 96 +++++++++++++++++++++++-- app/widget/viewer/viewer.h | 17 +++++ app/window/mainwindow/mainstatusbar.cpp | 1 + 19 files changed, 180 insertions(+), 14 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 7f866a8ff..479f5b923 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -83,6 +83,9 @@ void Config::SetDefaults() config_map_["DropWithoutSequenceBehavior"] = TimelineWidget::kDWSAsk; config_map_["Loop"] = false; + config_map_["AutoCache"] = true; + config_map_["AutoCacheInterval"] = 1000; + config_map_["NodeCatColor0"] = QVariant::fromValue(Color(0.75f, 0.75f, 0.75f)); config_map_["NodeCatColor1"] = QVariant::fromValue(Color(0.25f, 0.25f, 0.25f)); config_map_["NodeCatColor2"] = QVariant::fromValue(Color(0.75f, 0.75f, 0.25f)); diff --git a/app/core.cpp b/app/core.cpp index 0434a06e4..7580268d1 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -58,6 +58,7 @@ #include "undo/undostack.h" #include "widget/menu/menushared.h" #include "widget/taskview/taskviewitem.h" +#include "widget/viewer/viewer.h" #include "window/mainwindow/mainwindow.h" OLIVE_NAMESPACE_ENTER @@ -1134,6 +1135,9 @@ void Core::CacheActiveSequence(bool in_out_only) arp, in_out_only); + // Stop any current auto-cache tasks + ViewerWidget::StopAllBackgroundCacheTasks(); + TaskDialog* dialog = new TaskDialog(task, tr("Caching Sequence"), main_window_); dialog->open(); } diff --git a/app/panel/taskmanager/taskmanager.cpp b/app/panel/taskmanager/taskmanager.cpp index 16cdd3a5c..e77f4e39f 100644 --- a/app/panel/taskmanager/taskmanager.cpp +++ b/app/panel/taskmanager/taskmanager.cpp @@ -37,6 +37,7 @@ TaskManagerPanel::TaskManagerPanel(QWidget* parent) : connect(TaskManager::instance(), &TaskManager::TaskAdded, view_, &TaskView::AddTask); connect(TaskManager::instance(), &TaskManager::TaskRemoved, view_, &TaskView::RemoveTask); connect(TaskManager::instance(), &TaskManager::TaskFailed, view_, &TaskView::TaskFailed); + connect(view_, &TaskView::TaskCancelled, TaskManager::instance(), &TaskManager::CancelTask); // Set strings Retranslate(); diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index caace5735..de5254e54 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -204,6 +204,10 @@ void RenderBackend::UpdateInstance(RenderWorker *instance) void RenderBackend::Close() { + video_pool_.threads.clear(); + audio_pool_.threads.clear(); + hash_pool_.threads.clear(); + CancelQueue(); video_pool_.Destroy(); diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index d0b38f88c..c7429749c 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -135,6 +135,11 @@ QList FrameHashCache::GetFrameListFromTimeRange(const TimeRangeList &r return GetFrameListFromTimeRange(range, timebase_); } +QList FrameHashCache::GetInvalidatedFrames() const +{ + return GetFrameListFromTimeRange(GetInvalidatedRanges()); +} + void FrameHashCache::SaveCacheFrame(const QByteArray& hash, char* data, const VideoRenderingParams& vparam) diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index fa5f66410..0cc19c888 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -68,6 +68,7 @@ public: static QList GetFrameListFromTimeRange(TimeRangeList range_list, const rational& timebase); QList GetFrameListFromTimeRange(const TimeRangeList &range) const; + QList GetInvalidatedFrames() const; protected: virtual void LengthChangedEvent(const rational& old, const rational& newlen) override; diff --git a/app/task/cache/cache.cpp b/app/task/cache/cache.cpp index 44b717ae9..0a4b99df2 100644 --- a/app/task/cache/cache.cpp +++ b/app/task/cache/cache.cpp @@ -50,12 +50,14 @@ bool CacheTask::Run() Render(range_to_cache, QMatrix4x4(), false); + download_threads_.waitForDone(); + return true; } QFuture CacheTask::DownloadFrame(FramePtr frame, const QByteArray &hash) { - return QtConcurrent::run(FrameHashCache::SaveCacheFrame, hash, frame); + return QtConcurrent::run(&download_threads_, FrameHashCache::SaveCacheFrame, hash, frame); } void CacheTask::FrameDownloaded(const QByteArray &hash, const QLinkedList ×) diff --git a/app/task/cache/cache.h b/app/task/cache/cache.h index fce79c154..d49afa53e 100644 --- a/app/task/cache/cache.h +++ b/app/task/cache/cache.h @@ -47,6 +47,8 @@ protected: private: bool in_out_only_; + QThreadPool download_threads_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index f50d33646..847c7e813 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -92,7 +92,7 @@ void RenderTask::Render(const TimeRangeList& range_to_cache, QLinkedList sorted_times; QLinkedList::iterator sorted_iterator; - // Rendering is more efficient if we cache in order + // Rendering is more efficient if we cache in order, so here we sort QMap< QByteArray, QLinkedList >::const_iterator i; for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) { const QByteArray& hash = i.key(); @@ -141,9 +141,10 @@ void RenderTask::Render(const TimeRangeList& range_to_cache, QLinkedList::iterator j; QLinkedList::iterator k; - while (!render_lookup_table.isEmpty() - || !download_futures.isEmpty() - || !audio_lookup_table.isEmpty()) { + while (!IsCancelled() + && (!render_lookup_table.isEmpty() + || !download_futures.isEmpty() + || !audio_lookup_table.isEmpty())) { i = render_lookup_table.begin(); @@ -197,4 +198,9 @@ void RenderTask::AudioDownloaded(const TimeRange &, SampleBufferPtr) { } +void RenderTask::SetAnchorPoint(const rational &r) +{ + anchor_point_ = r; +} + OLIVE_NAMESPACE_EXIT diff --git a/app/task/render/render.h b/app/task/render/render.h index 4a6889ba3..dfd8e0bc4 100644 --- a/app/task/render/render.h +++ b/app/task/render/render.h @@ -57,6 +57,8 @@ protected: return audio_params_; } + void SetAnchorPoint(const rational& r); + private: ViewerOutput* viewer_; @@ -64,6 +66,8 @@ private: AudioRenderingParams audio_params_; + rational anchor_point_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/task/taskmanager.cpp b/app/task/taskmanager.cpp index d16f3c549..a65888bee 100644 --- a/app/task/taskmanager.cpp +++ b/app/task/taskmanager.cpp @@ -72,6 +72,17 @@ Task *TaskManager::GetFirstTask() const return tasks_.begin().value(); } +void TaskManager::CancelTaskAndWait(Task* t) +{ + t->Cancel(); + + QFutureWatcher* w = tasks_.key(t); + + if (w) { + w->waitForFinished(); + } +} + void TaskManager::AddTask(Task* t) { // Create a watcher for signalling @@ -89,6 +100,17 @@ void TaskManager::AddTask(Task* t) emit TaskListChanged(); } +void TaskManager::CancelTask(Task *t) +{ + if (failed_tasks_.contains(t)) { + failed_tasks_.removeOne(t); + emit TaskRemoved(t); + t->deleteLater(); + } else { + t->Cancel(); + } +} + void TaskManager::TaskFinished() { QFutureWatcher* watcher = static_cast*>(sender()); diff --git a/app/task/taskmanager.h b/app/task/taskmanager.h index 139cdd7be..497c1d8cf 100644 --- a/app/task/taskmanager.h +++ b/app/task/taskmanager.h @@ -63,6 +63,8 @@ public: Task* GetFirstTask() const; + void CancelTaskAndWait(Task* t); + public slots: /** * @brief Add a new Task @@ -81,6 +83,8 @@ public slots: */ void AddTask(Task *t); + void CancelTask(Task* t); + signals: /** * @brief Signal emitted when a Task is added by AddTask() diff --git a/app/widget/taskview/taskview.cpp b/app/widget/taskview/taskview.cpp index 15625eda1..f3c264248 100644 --- a/app/widget/taskview/taskview.cpp +++ b/app/widget/taskview/taskview.cpp @@ -47,6 +47,7 @@ void TaskView::AddTask(Task *t) { // Create TaskViewItem (UI representation of a Task) and connect it TaskViewItem* item = new TaskViewItem(t); + connect(item, &TaskViewItem::TaskCancelled, this, &TaskView::TaskCancelled); items_.insert(t, item); layout_->insertWidget(layout_->count()-1, item); } diff --git a/app/widget/taskview/taskview.h b/app/widget/taskview/taskview.h index 18acb9346..19862c6a1 100644 --- a/app/widget/taskview/taskview.h +++ b/app/widget/taskview/taskview.h @@ -42,6 +42,9 @@ class TaskView : public QScrollArea public: TaskView(QWidget* parent); +signals: + void TaskCancelled(Task* t); + public slots: /** * @brief Creates a TaskViewItem, connects it to a Task, and adds it to this widget diff --git a/app/widget/taskview/taskviewitem.cpp b/app/widget/taskview/taskviewitem.cpp index 00c597f19..b3438791c 100644 --- a/app/widget/taskview/taskviewitem.cpp +++ b/app/widget/taskview/taskviewitem.cpp @@ -61,12 +61,13 @@ TaskViewItem::TaskViewItem(Task* task, QWidget *parent) : // Connect to the task connect(task_, &Task::ProgressChanged, this, &TaskViewItem::UpdateProgress); - connect(cancel_btn_, &QPushButton::clicked, task_, &Task::Cancel, Qt::DirectConnection); + connect(cancel_btn_, &QPushButton::clicked, this, [this] { emit TaskCancelled(task_); }); } void TaskViewItem::Failed() { - task_status_lbl_->setText(task_->GetError()); + task_status_lbl_->setStyleSheet("color: red"); + task_status_lbl_->setText(tr("Error: %1").arg(task_->GetError())); } void TaskViewItem::UpdateProgress(double d) diff --git a/app/widget/taskview/taskviewitem.h b/app/widget/taskview/taskviewitem.h index a727a68f5..5063834e2 100644 --- a/app/widget/taskview/taskviewitem.h +++ b/app/widget/taskview/taskviewitem.h @@ -47,6 +47,9 @@ public: void Failed(); +signals: + void TaskCancelled(Task* t); + private: QLabel* task_name_lbl_; QProgressBar* progress_bar_; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 9107b4c6e..ba810adb8 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -36,6 +36,7 @@ #include "project/item/sequence/sequence.h" #include "project/project.h" #include "render/pixelformat.h" +#include "task/taskmanager.h" #include "widget/menu/menu.h" #include "window/mainwindow/mainwindow.h" @@ -43,6 +44,8 @@ OLIVE_NAMESPACE_ENTER const int kMaxPreQueueSize = 16; +CacheTask* ViewerWidget::cache_background_task_ = nullptr; + ViewerWidget::ViewerWidget(QWidget *parent) : TimeBasedWidget(false, true, parent), playback_speed_(0), @@ -102,11 +105,19 @@ ViewerWidget::ViewerWidget(QWidget *parent) : // FIXME: Magic number SetScale(48.0); - // Start background renderers + // Start background renderer renderer_ = new OpenGLBackend(this); + // Setup cache wait timer (waits a few seconds of inactivity before caching) + cache_wait_timer_.setInterval(1000); + cache_wait_timer_.setSingleShot(true); + connect(&cache_wait_timer_, &QTimer::timeout, this, &ViewerWidget::StartBackgroundCaching); + connect(TaskManager::instance(), &TaskManager::TaskRemoved, this, &ViewerWidget::BackgroundCacheFinished); + + // Ensures that seeking on the waveform view updates the time as expected connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal); + // Ensures renderer is updated if the global pixel format is changed connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters); SetAutoMaxScrollBar(true); @@ -196,6 +207,8 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) // Set texture to new texture (or null if no viewer node is available) ForceUpdate(); + + StartBackgroundCaching(); } void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) @@ -223,6 +236,10 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) waveform_view_->SetViewer(nullptr); waveform_view_->ConnectTimelinePoints(nullptr); + + StopAllBackgroundCacheTasks(); + cache_background_task_ = nullptr; + cache_wait_timer_.stop(); } void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n) @@ -343,6 +360,16 @@ void ViewerWidget::SetGizmos(Node *node) display_widget_->SetGizmos(node); } +void ViewerWidget::StopAllBackgroundCacheTasks() +{ + TaskManager::instance()->CancelTaskAndWait(cache_background_task_); +} + +void ViewerWidget::SetBackgroundCacheTask(CacheTask *t) +{ + cache_background_task_ = t; +} + FramePtr DecodeCachedImage(const QString &fn, const rational& time) { FramePtr frame = nullptr; @@ -453,6 +480,8 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) } else { FillPlaybackQueue(); } + + StopBackgroundCache(); } void ViewerWidget::PushScrubbedAudio() @@ -614,6 +643,29 @@ void ViewerWidget::FinishPlayPreprocess() } } +VideoRenderingParams ViewerWidget::GenerateVideoParams() const +{ + return VideoRenderingParams(GetConnectedNode()->video_params(), + GetCurrentPixelFormat(), + RenderMode::kOffline, + divider_); +} + +AudioRenderingParams ViewerWidget::GenerateAudioParams() const +{ + return AudioRenderingParams(GetConnectedNode()->audio_params(), + SampleFormat::kInternalFormat); +} + +void ViewerWidget::StopBackgroundCache() +{ + cache_wait_timer_.stop(); + if (cache_background_task_) { + cache_background_task_->Cancel(); + cache_background_task_ = nullptr; + } +} + void ViewerWidget::UpdateStack() { if (GetConnectedNode() @@ -753,15 +805,38 @@ void ViewerWidget::HashGenerated() watcher->deleteLater(); } +void ViewerWidget::StartBackgroundCaching() +{ + if (Config::Current()["AutoCache"].toBool()) { + if (cache_background_task_) { + + // Something else is caching right now, we don't want to do multiple at once so we'll check + // again in our next interval + cache_wait_timer_.start(); + + } else { + cache_background_task_ = new CacheTask(GetConnectedNode(), + GenerateVideoParams(), + GenerateAudioParams(), + false); + + TaskManager::instance()->AddTask(cache_background_task_); + } + } +} + +void ViewerWidget::BackgroundCacheFinished(Task* t) +{ + if (cache_background_task_ == t) { + cache_background_task_ = nullptr; + } +} + void ViewerWidget::UpdateRendererParameters() { - renderer_->SetVideoParams(VideoRenderingParams(GetConnectedNode()->video_params(), - GetCurrentPixelFormat(), - RenderMode::kOffline, - divider_)); + renderer_->SetVideoParams(GenerateVideoParams()); - renderer_->SetAudioParams(AudioRenderingParams(GetConnectedNode()->audio_params(), - SampleFormat::kInternalFormat)); + renderer_->SetAudioParams(GenerateAudioParams()); display_widget_->SetVideoParams(GetConnectedNode()->video_params()); } @@ -923,6 +998,7 @@ void ViewerWidget::Pause() } prequeuing_ = false; + StartBackgroundCaching(); } void ViewerWidget::ShuttleLeft() @@ -1093,6 +1169,11 @@ void ViewerWidget::ViewerInvalidatedRange(const TimeRange &range) ForceUpdate(); } + // Restart the cache wait timer + StopBackgroundCache(); + cache_wait_timer_.start(); + + /* QList invalidated_frames = GetConnectedNode()->video_frame_cache()->GetFrameListFromTimeRange({range}); foreach (const rational& r, invalidated_frames) { QFutureWatcher* watcher = new QFutureWatcher(); @@ -1100,6 +1181,7 @@ void ViewerWidget::ViewerInvalidatedRange(const TimeRange &range) hash_watchers_.insert(watcher, r); watcher->setFuture(renderer_->Hash(r, true)); } + */ } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 6d7516cc8..3c9dc74e6 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -33,6 +33,7 @@ #include "node/output/viewer/viewer.h" #include "panel/scope/scope.h" #include "render/backend/opengl/openglbackend.h" +#include "task/cache/cache.h" #include "viewerdisplay.h" #include "viewerplaybacktimer.h" #include "viewerqueue.h" @@ -97,6 +98,9 @@ public: void SetGizmos(Node* node); + static void StopAllBackgroundCacheTasks(); + static void SetBackgroundCacheTask(CacheTask* t); + public slots: void Play(bool in_to_out_only); @@ -192,6 +196,11 @@ private: void FinishPlayPreprocess(); + VideoRenderingParams GenerateVideoParams() const; + AudioRenderingParams GenerateAudioParams() const; + + void StopBackgroundCache(); + QStackedWidget* stack_; ViewerSizer* sizer_; @@ -235,6 +244,10 @@ private: QHash*, rational> hash_watchers_; + QTimer cache_wait_timer_; + + static CacheTask* cache_background_task_; + private slots: void PlaybackTimerUpdate(); @@ -272,6 +285,10 @@ private slots: void HashGenerated(); + void StartBackgroundCaching(); + + void BackgroundCacheFinished(Task *t); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/window/mainwindow/mainstatusbar.cpp b/app/window/mainwindow/mainstatusbar.cpp index 0d0815c0e..bf3532dc6 100644 --- a/app/window/mainwindow/mainstatusbar.cpp +++ b/app/window/mainwindow/mainstatusbar.cpp @@ -62,6 +62,7 @@ void MainStatusBar::UpdateStatus() if (manager_->GetTaskCount() == 0) { clearMessage(); bar_->setVisible(false); + bar_->setValue(0); } else { Task* t = manager_->GetFirstTask();