From c8def73b95b82772b0be87243061379de483dbc0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 5 Aug 2020 01:56:28 +1000 Subject: [PATCH] cache: mild auto-cache overhaul Shifted from CacheTask to functionality built into RenderBackend. It was a lot easier to control behavior this way without having to juggle a ton of threads and race conditions. Could likely be multithreaded further. --- app/core.cpp | 35 +- app/node/output/viewer/viewer.cpp | 4 +- app/node/output/viewer/viewer.h | 3 +- app/panel/viewer/viewerbase.cpp | 10 + app/panel/viewer/viewerbase.h | 4 + app/render/backend/renderbackend.cpp | 402 ++++++++++++++++++++--- app/render/backend/renderbackend.h | 122 ++++++- app/render/backend/renderticketwatcher.h | 5 + app/render/framehashcache.cpp | 61 +++- app/render/framehashcache.h | 9 +- app/task/cache/cache.cpp | 2 +- app/task/render/render.cpp | 8 +- app/widget/viewer/viewer.cpp | 271 ++++++--------- app/widget/viewer/viewer.h | 40 +-- 14 files changed, 692 insertions(+), 284 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index d7014f408..25a0e6d07 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1267,23 +1267,32 @@ void Core::CacheActiveSequence(bool in_out_only) TimeBasedPanel* p = PanelManager::instance()->MostRecentlyFocused(); if (p && p->GetConnectedViewer()) { - CacheTask* task = new CacheTask(p->GetConnectedViewer(), - p->GetConnectedViewer()->video_params(), - p->GetConnectedViewer()->audio_params(), - in_out_only); + // Hacky but works for now - // Stop any current auto-cache tasks - ViewerWidget::StopAllBackgroundCacheTasks(true); - ViewerWidget::SetBackgroundCacheTask(task); + // Find Viewer attached to this TimeBasedPanel + QList all_viewers = PanelManager::instance()->GetPanelsOfType(); - TaskDialog* dialog = new TaskDialog(task, tr("Caching Sequence"), main_window_); + ViewerPanel* found_panel = nullptr; - connect(dialog, - &TaskDialog::TaskSucceeded, - this, - [] { ViewerWidget::SetBackgroundCacheTask(nullptr); }); + foreach (ViewerPanel* viewer, all_viewers) { + if (viewer->GetConnectedViewer() == p->GetConnectedViewer()) { + found_panel = viewer; + break; + } + } - dialog->open(); + if (found_panel) { + if (in_out_only) { + found_panel->CacheSequenceInOut(); + } else { + found_panel->CacheEntireSequence(); + } + } else { + QMessageBox::critical(main_window_, + tr("Failed to cache sequence"), + tr("No active viewer found with this sequence."), + QMessageBox::Ok); + } } } diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 457ce99de..0d96a049e 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -148,14 +148,14 @@ void ViewerOutput::set_video_params(const VideoParams &video) emit TimebaseChanged(video_params_.time_base()); } - emit ParamsChanged(); + emit VideoParamsChanged(); } void ViewerOutput::set_audio_params(const AudioParams &audio) { audio_params_ = audio; - emit ParamsChanged(); + emit AudioParamsChanged(); } rational ViewerOutput::GetLength() diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index e57406c46..c2b1c49e1 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -131,7 +131,8 @@ signals: void SizeChanged(int width, int height); - void ParamsChanged(); + void VideoParamsChanged(); + void AudioParamsChanged(); void BlockAdded(Block* block, TrackReference track); void BlockRemoved(Block* block); diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp index 13a8c7394..47ebbe0e0 100644 --- a/app/panel/viewer/viewerbase.cpp +++ b/app/panel/viewer/viewerbase.cpp @@ -90,6 +90,16 @@ void ViewerPanelBase::SetGizmos(Node *node) static_cast(GetTimeBasedWidget())->SetGizmos(node); } +void ViewerPanelBase::CacheEntireSequence() +{ + static_cast(GetTimeBasedWidget())->CacheEntireSequence(); +} + +void ViewerPanelBase::CacheSequenceInOut() +{ + static_cast(GetTimeBasedWidget())->CacheSequenceInOut(); +} + void ViewerPanelBase::CreateScopePanel(ScopePanel::Type type) { ViewerWidget* vw = static_cast(GetTimeBasedWidget()); diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index a3261da50..36ac0aa35 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -57,6 +57,10 @@ public: public slots: void SetGizmos(Node* node); + void CacheEntireSequence(); + + void CacheSequenceInOut(); + protected: void CreateScopePanel(ScopePanel::Type type); diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index aa891e722..75a609846 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -31,13 +31,27 @@ OLIVE_NAMESPACE_ENTER +QVector RenderBackend::instances_; +QMutex RenderBackend::instance_lock_; +RenderBackend* RenderBackend::active_instance_ = nullptr; +QThreadPool RenderBackend::thread_pool_; + RenderBackend::RenderBackend(QObject *parent) : QObject(parent), viewer_node_(nullptr), - update_with_graph_(false), + autocache_enabled_(false), + autocache_paused_(false), preview_job_time_(0), - render_mode_(RenderMode::kOnline) + render_mode_(RenderMode::kOnline), + autocache_has_changed_(false), + use_custom_autocache_range_(false) { + instance_lock_.lock(); + instances_.append(this); + instance_lock_.unlock(); + + // Set default autocache range + SetAutoCachePlayhead(rational()); } RenderBackend::~RenderBackend() @@ -53,22 +67,21 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) ViewerOutput* old_viewer = viewer_node_; if (!viewer_node) { - // If setting to null, set it here before we wait for jobs to finish - viewer_node_ = viewer_node; + // If setting to null, set it here before we wait for jobs to finish to prevent WorkerFinished() + // from calling RunNextJob() again and preventing us from finishing + viewer_node_ = nullptr; } if (old_viewer) { - // Delete all of our copied nodes - pool_.clear(); - pool_.waitForDone(); + // Cancel any remaining tickets + ClearQueue(); - // Cancel all tickets - foreach (RenderTicketPtr t, render_queue_) { - t->Cancel(); + // Wait for any currently running jobs to finish + foreach (RenderTicketPtr ticket, running_tickets_) { + ticket->WaitForFinished(); } - render_queue_.clear(); - // Delete all the nodes + // Delete all of our copied nodes foreach (Node* c, copy_map_) { c->deleteLater(); } @@ -76,18 +89,27 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) copied_viewer_node_ = nullptr; graph_update_queue_.clear(); + // Disconnect signal (will be a no-op if the signal was never connected) disconnect(old_viewer, &ViewerOutput::GraphChangedFrom, this, &RenderBackend::NodeGraphChanged); + + disconnect(old_viewer->video_frame_cache(), + &PlaybackCache::Invalidated, + this, + &RenderBackend::AutoCacheVideoInvalidated); + + disconnect(old_viewer->audio_playback_cache(), + &PlaybackCache::Invalidated, + this, + &RenderBackend::AutoCacheAudioInvalidated); } if (viewer_node) { // If setting to non-null, set it now viewer_node_ = viewer_node; - } - if (viewer_node_) { // Copy graph copied_viewer_node_ = static_cast(viewer_node_->copy()); copy_map_.insert(viewer_node_, copied_viewer_node_); @@ -96,65 +118,92 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) NodeGraphChanged(viewer_node_->samples_input()); ProcessUpdateQueue(); - if (update_with_graph_) { + if (autocache_enabled_) { connect(viewer_node_, &ViewerOutput::GraphChangedFrom, this, &RenderBackend::NodeGraphChanged); + + connect(viewer_node_->video_frame_cache(), + &PlaybackCache::Invalidated, + this, + &RenderBackend::AutoCacheVideoInvalidated); + + connect(viewer_node_->audio_playback_cache(), + &PlaybackCache::Invalidated, + this, + &RenderBackend::AutoCacheAudioInvalidated); } } } -void RenderBackend::ClearVideoQueue() +void RenderBackend::AutoCacheRange(const TimeRange &range) { - foreach (RenderTicketPtr t, render_queue_) { - t->Cancel(); - } - render_queue_.clear(); + Q_ASSERT(autocache_enabled_); + + autocache_has_changed_ = true; + use_custom_autocache_range_ = true; + custom_autocache_range_ = range; + + AutoCacheRequeueFrames(); } -RenderTicketPtr RenderBackend::Hash(const QVector ×) +RenderTicketPtr RenderBackend::Hash(const QVector ×, bool prioritize) { - if (!viewer_node_) { - return nullptr; - } + Q_ASSERT(viewer_node_); + + SetActiveInstance(); RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeHash, QVariant::fromValue(times)); - render_queue_.push_back(ticket); + if (prioritize) { + render_queue_.push_front(ticket); + } else { + render_queue_.push_back(ticket); + } QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection); return ticket; } -RenderTicketPtr RenderBackend::RenderFrame(const rational &time) +RenderTicketPtr RenderBackend::RenderFrame(const rational &time, bool prioritize, const QByteArray& hash) { - if (!viewer_node_) { - return nullptr; - } + Q_ASSERT(viewer_node_); + + SetActiveInstance(); RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeVideo, QVariant::fromValue(time)); - render_queue_.push_back(ticket); + ticket->setProperty("hash", hash); + + if (prioritize) { + render_queue_.push_front(ticket); + } else { + render_queue_.push_back(ticket); + } QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection); return ticket; } -RenderTicketPtr RenderBackend::RenderAudio(const TimeRange &r) +RenderTicketPtr RenderBackend::RenderAudio(const TimeRange &r, bool prioritize) { - if (!viewer_node_) { - return nullptr; - } + Q_ASSERT(viewer_node_); + + SetActiveInstance(); RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeAudio, QVariant::fromValue(r)); - render_queue_.push_back(ticket); + if (prioritize) { + render_queue_.push_front(ticket); + } else { + render_queue_.push_back(ticket); + } QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection); @@ -194,6 +243,27 @@ std::list RenderBackend::SplitRangeIntoChunks(const TimeRange &r) return split_ranges; } +void RenderBackend::ClearVideoQueue() +{ + ClearQueueOfType(RenderTicket::kTypeVideo); + + autocache_has_changed_ = true; + use_custom_autocache_range_ = false; +} + +void RenderBackend::ClearAudioQueue() +{ + ClearQueueOfType(RenderTicket::kTypeAudio); +} + +void RenderBackend::ClearQueue() +{ + foreach (RenderTicketPtr t, render_queue_) { + t->Cancel(); + } + render_queue_.clear(); +} + void RenderBackend::NodeGraphChanged(NodeInput *source) { // We need to determine: @@ -259,6 +329,14 @@ void RenderBackend::RunNextJob() { // If queue is empty, nothing to be done if (render_queue_.empty()) { + + // If we're the active instance, unset it + instance_lock_.lock(); + if (active_instance_ == this) { + active_instance_ = nullptr; + } + instance_lock_.unlock(); + return; } @@ -270,7 +348,7 @@ void RenderBackend::RunNextJob() } // If we have a value update queued, check if all workers are available and proceed from there - if (update_with_graph_ && !graph_update_queue_.isEmpty()) { + if (autocache_enabled_ && !graph_update_queue_.isEmpty()) { bool all_workers_available = true; foreach (const WorkerData& data, workers_) { @@ -291,7 +369,7 @@ void RenderBackend::RunNextJob() // If we have no workers allocated, allocate them now if (workers_.isEmpty()) { // Allocate workers here - workers_.resize(pool_.maxThreadCount()); + workers_.resize(thread_pool_.maxThreadCount()); for (int i=0;iSetTicket(ticket); + switch (ticket->GetType()) { case RenderTicket::kTypeHash: - QtConcurrent::run(&pool_, + QtConcurrent::run(&thread_pool_, worker, &RenderWorker::Hash, ticket, @@ -333,15 +416,24 @@ void RenderBackend::RunNextJob() ticket->GetTime().value >()); break; case RenderTicket::kTypeVideo: - QtConcurrent::run(&pool_, + { + rational frame = ticket->GetTime().value(); + + QtConcurrent::run(&thread_pool_, worker, &RenderWorker::RenderFrame, ticket, copied_viewer_node_, - ticket->GetTime().value()); + frame); + + QByteArray frame_hash = ticket->property("hash").toByteArray(); + if (!frame_hash.isEmpty()) { + currently_caching_hashes_.append(frame_hash); + } break; + } case RenderTicket::kTypeAudio: - QtConcurrent::run(&pool_, + QtConcurrent::run(&thread_pool_, worker, &RenderWorker::RenderAudio, ticket, @@ -358,6 +450,162 @@ void RenderBackend::RunNextJob() } } +void RenderBackend::TicketFinished() +{ + RenderTicketPtr ticket = static_cast(sender())->GetTicket(); + delete sender(); + + running_tickets_.remove(ticket); +} + +void RenderBackend::AutoCacheVideoInvalidated(const TimeRange &range) +{ + ClearVideoQueue(); + + // Hash these frames since that should be relatively quick. + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + QVector frames = viewer_node_->video_frame_cache()->GetFrameListFromTimeRange({range}); + autocache_hash_tasks_.insert(watcher, {frames, QDateTime::currentMSecsSinceEpoch()}); + connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::AutoCacheHashesGenerated); + watcher->SetTicket(Hash(frames)); +} + +void RenderBackend::AutoCacheAudioInvalidated(const TimeRange &range) +{ + // Start a task to re-render the audio at this range + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + autocache_audio_tasks_.insert(watcher, {range, QDateTime::currentMSecsSinceEpoch()}); + connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::AutoCacheAudioRendered); + watcher->SetTicket(RenderAudio(range, true)); +} + +void RenderBackend::SetHashes(FrameHashCache* cache, const QVector& times, const QVector& hashes, qint64 job_time) +{ + std::vector existing_hashes; + + for (int i=0; iCachePathName(hash)); + + if (hash_exists) { + existing_hashes.push_back(hash); + } + } + + cache->SetHash(time, hash, job_time, hash_exists); + } +} + +void RenderBackend::AutoCacheHashesGenerated() +{ + RenderTicketWatcher* watcher = static_cast(sender()); + + if (autocache_hash_tasks_.contains(watcher)) { + if (!watcher->WasCancelled()) { + const HashJobInfo& info = autocache_hash_tasks_.value(watcher); + + QFutureWatcher* hw = new QFutureWatcher(); + connect(hw, &QFutureWatcher::finished, this, &RenderBackend::AutoCacheHashesProcessed); + autocache_hash_process_tasks_.append(hw); + hw->setFuture(QtConcurrent::run(this, + &RenderBackend::SetHashes, + viewer_node_->video_frame_cache(), + info.times, + watcher->Get().value >(), + info.job_time)); + } + + autocache_hash_tasks_.remove(watcher); + } + + delete watcher; +} + +void RenderBackend::AutoCacheHashesProcessed() +{ + QFutureWatcher* watcher = static_cast*>(sender()); + + if (autocache_hash_process_tasks_.contains(watcher)) { + autocache_hash_process_tasks_.removeOne(watcher); + + AutoCacheRequeueFrames(); + } + + delete watcher; +} + +void RenderBackend::AutoCacheAudioRendered() +{ + RenderTicketWatcher* watcher = static_cast(sender()); + + if (autocache_audio_tasks_.contains(watcher)) { + if (!watcher->WasCancelled()) { + const AudioJobInfo& job_info = autocache_audio_tasks_.value(watcher); + + viewer_node_->audio_playback_cache()->WritePCM(job_info.range, + watcher->Get().value(), + job_info.job_time); + } + + autocache_audio_tasks_.remove(watcher); + } + + delete watcher; +} + +void RenderBackend::AutoCacheVideoRendered() +{ + RenderTicketWatcher* watcher = static_cast(sender()); + + if (autocache_video_tasks_.contains(watcher)) { + if (!watcher->WasCancelled()) { + const VideoJobInfo& info = autocache_video_tasks_.value(watcher); + + // Download frame in another thread + QFutureWatcher* w = new QFutureWatcher(); + autocache_video_download_tasks_.insert(w, info); + connect(w, &QFutureWatcher::finished, this, &RenderBackend::AutoCacheVideoDownloaded); + w->setFuture(QtConcurrent::run(FrameHashCache::SaveCacheFrame, + info.hash, + watcher->Get().value())); + } + + autocache_video_tasks_.remove(watcher); + } + + delete watcher; +} + +void RenderBackend::AutoCacheVideoDownloaded() +{ + QFutureWatcher* watcher = static_cast*>(sender()); + + if (autocache_video_download_tasks_.contains(watcher)) { + if (!watcher->isCanceled()) { + if (watcher->result()) { + const VideoJobInfo& info = autocache_video_download_tasks_.value(watcher); + + currently_caching_hashes_.removeOne(info.hash); + + viewer_node_->video_frame_cache()->ValidateFramesWithHash(info.hash); + } else { + qCritical() << "Failed to download video frame"; + } + } + + autocache_video_download_tasks_.remove(watcher); + } + + delete watcher; +} + //#define PRINT_UPDATE_QUEUE_INFO void RenderBackend::ProcessUpdateQueue() { @@ -482,4 +730,76 @@ void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_ } } +void RenderBackend::ClearQueueOfType(RenderTicket::Type type) +{ + std::list::iterator i = render_queue_.begin(); + + while (i != render_queue_.end()) { + if ((*i)->GetType() == type) { + (*i)->Cancel(); + i = render_queue_.erase(i); + } else { + i++; + } + } +} + +void RenderBackend::SetActiveInstance() +{ + QMutexLocker locker(&instance_lock_); + + if (active_instance_ != this) { + // Signal active instance to stop + QMetaObject::invokeMethod(active_instance_, "ClearVideoQueue", Qt::QueuedConnection); + + active_instance_ = this; + } +} + +void RenderBackend::AutoCacheRequeueFrames() +{ + if (viewer_node_ + && viewer_node_->video_frame_cache()->HasInvalidatedRanges() + && autocache_hash_tasks_.isEmpty() + && autocache_hash_process_tasks_.isEmpty() + && autocache_has_changed_ + && (!autocache_paused_ || use_custom_autocache_range_)) { + TimeRange using_range; + + if (use_custom_autocache_range_) { + using_range = custom_autocache_range_; + use_custom_autocache_range_ = false; + } else { + using_range = autocache_range_; + } + + QVector invalidated_ranges = viewer_node_->video_frame_cache()->GetInvalidatedFrames(using_range); + + ClearVideoQueue(); + + // QMaps are automatically sorted by time which is always best for rendering + QList queued_hashes; + + foreach (const rational& t, invalidated_ranges) { + const QByteArray& hash = viewer_node_->video_frame_cache()->GetHash(t); + + if (t >= using_range.in() + && t < using_range.out() + && !queued_hashes.contains(hash) + && !currently_caching_hashes_.contains(hash)) { + // Don't render any hash more than once + queued_hashes.append(hash); + + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::AutoCacheVideoRendered); + autocache_video_tasks_.insert(watcher, {hash, QDateTime::currentMSecsSinceEpoch()}); + + watcher->SetTicket(RenderFrame(t, false, hash)); + } + } + + autocache_has_changed_ = false; + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index 19ab3948f..1f0b5879d 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -23,12 +23,14 @@ #include +#include "config/config.h" #include "dialog/rendercancel/rendercancel.h" #include "decodercache.h" #include "node/graph.h" #include "node/output/viewer/viewer.h" #include "render/backend/colorprocessorcache.h" #include "renderticket.h" +#include "renderticketwatcher.h" #include "renderworker.h" OLIVE_NAMESPACE_ENTER @@ -50,9 +52,42 @@ public: void SetViewerNode(ViewerOutput* viewer_node); - void SetUpdateWithGraph(bool e) + void SetAutoCacheEnabled(bool e) { - update_with_graph_ = e; + autocache_enabled_ = e; + } + + bool IsAutoCachePaused() const + { + return autocache_paused_; + } + + void SetAutoCachePaused(bool paused) + { + autocache_paused_ = paused; + + if (autocache_paused_) { + // Pause the autocache + ClearVideoQueue(); + } else { + // Unpause the cache + AutoCacheRequeueFrames(); + } + } + + void AutoCacheRange(const TimeRange& range); + + void AutoCacheRequeueFrames(); + + void SetAutoCachePlayhead(const rational& playhead) + { + autocache_range_ = TimeRange(playhead - Config::Current()["DiskCacheBehind"].value(), + playhead + Config::Current()["DiskCacheAhead"].value()); + + autocache_has_changed_ = true; + use_custom_autocache_range_ = false; + + AutoCacheRequeueFrames(); } void SetRenderMode(RenderMode::Mode e) @@ -65,24 +100,22 @@ public: preview_job_time_ = job_time; } - void ClearVideoQueue(); - void ProcessUpdateQueue(); /** * @brief Asynchronously generate a hash at a given time */ - RenderTicketPtr Hash(const QVector ×); + RenderTicketPtr Hash(const QVector ×, bool prioritize = false); /** * @brief Asynchronously generate a frame at a given time */ - RenderTicketPtr RenderFrame(const rational& time); + RenderTicketPtr RenderFrame(const rational& time, bool prioritize = false, const QByteArray& hash = QByteArray()); /** * @brief Asynchronously generate a chunk of audio */ - RenderTicketPtr RenderAudio(const TimeRange& r); + RenderTicketPtr RenderAudio(const TimeRange& r, bool prioritize = false); const VideoParams& GetVideoParams() const { @@ -105,6 +138,14 @@ public: public slots: void NodeGraphChanged(NodeInput *source); + void ClearVideoQueue(); + + void ClearAudioQueue(); + + void ClearQueue(); + +signals: + protected: virtual RenderWorker* CreateNewWorker() = 0; @@ -113,6 +154,10 @@ private: Node *CopyNodeConnections(Node *src_node); void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input); + void ClearQueueOfType(RenderTicket::Type type); + + void SetHashes(FrameHashCache* cache, const QVector& times, const QVector& hashes, qint64 job_time); + ViewerOutput* viewer_node_; // VIDEO MEMBERS @@ -126,10 +171,10 @@ private: QHash copy_map_; ViewerOutput* copied_viewer_node_; - QThreadPool pool_; - std::list render_queue_; + std::list running_tickets_; + struct WorkerData { RenderWorker* worker; bool busy; @@ -137,17 +182,74 @@ private: QVector workers_; - bool update_with_graph_; + bool autocache_enabled_; + bool autocache_paused_; qint64 preview_job_time_; RenderMode::Mode render_mode_; + TimeRange autocache_range_; + + bool autocache_has_changed_; + + bool use_custom_autocache_range_; + TimeRange custom_autocache_range_; + + static QVector instances_; + static QMutex instance_lock_; + static RenderBackend* active_instance_; + static QThreadPool thread_pool_; + void SetActiveInstance(); + + struct HashJobInfo { + QVector times; + qint64 job_time; + }; + + struct AudioJobInfo { + TimeRange range; + qint64 job_time; + }; + + struct VideoJobInfo { + QByteArray hash; + qint64 job_time; + }; + + QMap autocache_hash_tasks_; + + QList*> autocache_hash_process_tasks_; + + QMap autocache_audio_tasks_; + + QMap autocache_video_tasks_; + + QMap*, VideoJobInfo> autocache_video_download_tasks_; + + QVector currently_caching_hashes_; + private slots: void WorkerFinished(); void RunNextJob(); + void TicketFinished(); + + void AutoCacheVideoInvalidated(const OLIVE_NAMESPACE::TimeRange &range); + + void AutoCacheAudioInvalidated(const OLIVE_NAMESPACE::TimeRange &range); + + void AutoCacheHashesGenerated(); + + void AutoCacheHashesProcessed(); + + void AutoCacheAudioRendered(); + + void AutoCacheVideoRendered(); + + void AutoCacheVideoDownloaded(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderticketwatcher.h b/app/render/backend/renderticketwatcher.h index e25c488fc..fba301a3b 100644 --- a/app/render/backend/renderticketwatcher.h +++ b/app/render/backend/renderticketwatcher.h @@ -31,6 +31,11 @@ class RenderTicketWatcher : public QObject public: RenderTicketWatcher(QObject* parent = nullptr); + RenderTicketPtr GetTicket() const + { + return ticket_; + } + void SetTicket(RenderTicketPtr ticket); bool WasCancelled(); diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index 34437fe9b..d215b6680 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -41,7 +41,7 @@ QByteArray FrameHashCache::GetHash(const rational &time) return time_hash_map_.value(time); } -void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, const qint64& job_time) +void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, const qint64& job_time, bool frame_exists) { QMutexLocker locker(lock()); @@ -63,13 +63,17 @@ void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, const time_hash_map_.insert(time, hash); - TimeRange validated_range(time, time + timebase_); - - NoLockValidate(validated_range); + TimeRange validated_range; + if (frame_exists) { + validated_range = TimeRange(time, time + timebase_); + NoLockValidate(validated_range); + } locker.unlock(); - emit Validated(validated_range); + if (frame_exists) { + emit Validated(validated_range); + } } void FrameHashCache::SetTimebase(const rational &tb) @@ -79,6 +83,33 @@ void FrameHashCache::SetTimebase(const rational &tb) timebase_ = tb; } +void FrameHashCache::ValidateFramesWithHash(const QByteArray &hash) +{ + QMutexLocker locker(lock()); + + QMap::const_iterator iterator; + + const TimeRangeList& invalidated_ranges = NoLockGetInvalidatedRanges(); + TimeRangeList ranges_validated; + + for (iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) { + if (iterator.value() == hash) { + TimeRange frame_range(iterator.key(), iterator.key() + timebase_); + + if (invalidated_ranges.ContainsTimeRange(frame_range)) { + NoLockValidate(frame_range); + ranges_validated.InsertTimeRange(frame_range); + } + } + } + + locker.unlock(); + + foreach (const TimeRange& range, ranges_validated) { + emit Validated(range); + } +} + QList FrameHashCache::GetFramesWithHash(const QByteArray &hash) { QMutexLocker locker(lock()); @@ -175,10 +206,17 @@ QVector FrameHashCache::GetInvalidatedFrames() { QMutexLocker locker(lock()); - return GetFrameListFromTimeRange(NoLockGetInvalidatedRanges()); + return GetFrameListFromTimeRange(NoLockGetInvalidatedRanges(), timebase_); } -void FrameHashCache::SaveCacheFrame(const QByteArray& hash, +QVector FrameHashCache::GetInvalidatedFrames(const TimeRange &intersecting) +{ + QMutexLocker locker(lock()); + + return GetFrameListFromTimeRange(NoLockGetInvalidatedRanges().Intersects(intersecting), timebase_); +} + +bool FrameHashCache::SaveCacheFrame(const QByteArray& hash, char* data, const VideoParams& vparam, int linesize_bytes) @@ -188,15 +226,20 @@ void FrameHashCache::SaveCacheFrame(const QByteArray& hash, if (SaveCacheFrame(fn, data, vparam, linesize_bytes)) { // Register frame with the disk manager DiskManager::instance()->CreatedFile(fn, hash); + + return true; + } else { + return false; } } -void FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) +bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) { if (frame) { - SaveCacheFrame(hash, frame->data(), frame->video_params(), frame->linesize_bytes()); + return SaveCacheFrame(hash, frame->data(), frame->video_params(), frame->linesize_bytes()); } else { qWarning() << "Attempted to save a NULL frame to the cache. This may or may not be desirable."; + return false; } } diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index 96878059e..ca320b029 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -44,6 +44,8 @@ public: void SetTimebase(const rational& tb); + void ValidateFramesWithHash(const QByteArray& hash); + /** * @brief Returns a list of frames that use a particular hash */ @@ -62,8 +64,8 @@ public: static QString CachePathName(const QByteArray &hash); static bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes); - static void SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes); - static void SaveCacheFrame(const QByteArray& hash, FramePtr frame); + static bool SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes); + static bool SaveCacheFrame(const QByteArray& hash, FramePtr frame); static FramePtr LoadCacheFrame(const QByteArray& hash); static FramePtr LoadCacheFrame(const QString& fn); @@ -72,9 +74,10 @@ public: static QVector GetFrameListFromTimeRange(TimeRangeList range_list, const rational& timebase); QVector GetFrameListFromTimeRange(const TimeRangeList &range); QVector GetInvalidatedFrames(); + QVector GetInvalidatedFrames(const TimeRange& intersecting); public slots: - void SetHash(const OLIVE_NAMESPACE::rational& time, const QByteArray& hash, const qint64 &job_time); + void SetHash(const OLIVE_NAMESPACE::rational& time, const QByteArray& hash, const qint64 &job_time, bool frame_exists); 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 bae2f6ab9..0b8d60279 100644 --- a/app/task/cache/cache.cpp +++ b/app/task/cache/cache.cpp @@ -72,7 +72,7 @@ QFuture CacheTask::DownloadFrame(FramePtr frame, const QByteArray &hash) void CacheTask::FrameDownloaded(const QByteArray &hash, const std::list ×) { foreach (const rational& t, times) { - viewer()->video_frame_cache()->SetHash(t, hash, job_time()); + viewer()->video_frame_cache()->SetHash(t, hash, job_time(), true); } } diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 1b93fe61e..5d81a8643 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -189,10 +189,12 @@ void RenderTask::Render(const TimeRangeList& video_range, while (!IsCancelled() && i != render_lookup_table.end()) { if (i->frame_future->IsFinished()) { - FramePtr f = i->frame_future->Get().value(); + if (!i->frame_future->WasCancelled()) { + FramePtr f = i->frame_future->Get().value(); - // Start multithreaded download here - download_futures.push_back({i->hash, DownloadFrame(f, i->hash)}); + // Start multithreaded download here + download_futures.push_back({i->hash, DownloadFrame(f, i->hash)}); + } i = render_lookup_table.erase(i); } else { diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 40bf5379a..82d70cc15 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -44,12 +44,10 @@ OLIVE_NAMESPACE_ENTER +QVector ViewerWidget::instances_; + const int kMaxPreQueueSize = 16; -CacheTask* ViewerWidget::cache_background_task_ = nullptr; - -int ViewerWidget::busy_viewers_ = 0; - ViewerWidget::ViewerWidget(QWidget *parent) : TimeBasedWidget(false, true, parent), playback_speed_(0), @@ -57,10 +55,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) : color_menu_enabled_(true), override_color_manager_(nullptr), time_changed_from_timer_(false), - prequeuing_(false), - busy_(false), - our_cache_background_task_(nullptr), - autocache_(true) + pause_autocache_during_playback_(true), + prequeuing_(false) { // Set up main layout QVBoxLayout* layout = new QVBoxLayout(this); @@ -112,26 +108,20 @@ ViewerWidget::ViewerWidget(QWidget *parent) : // Start background renderer renderer_ = new OpenGLBackend(this); - renderer_->SetUpdateWithGraph(true); + renderer_->SetAutoCacheEnabled(true); renderer_->SetRenderMode(RenderMode::kOffline); - // Setup cache wait timer (waits a few seconds of inactivity before caching) - cache_wait_timer_.setInterval(Config::Current()["AutoCacheInterval"].toInt()); - cache_wait_timer_.setSingleShot(true); - connect(&cache_wait_timer_, &QTimer::timeout, this, &ViewerWidget::StartBackgroundCaching); - - // Remove pointer to cache task if it's removed from the task manager - 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); + connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererVideoParameters); connect(&playback_backup_timer_, &QTimer::timeout, this, &ViewerWidget::PlaybackTimerUpdate); SetAutoMaxScrollBar(true); + + instances_.append(this); } ViewerWidget::~ViewerWidget() @@ -161,6 +151,10 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i) PushScrubbedAudio(); } + if (!pause_autocache_during_playback_ || !IsPlaying()) { + renderer_->SetAutoCachePlayhead(time_set); + } + display_widget_->SetTime(time_set); } @@ -171,10 +165,10 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) { connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot); connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); - connect(n, &ViewerOutput::ParamsChanged, this, &ViewerWidget::UpdateRendererParameters); + connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters); + connect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); connect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); connect(n->video_frame_cache(), &FrameHashCache::Shifted, this, &ViewerWidget::ViewerShiftedRange); - connect(n->audio_playback_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedRange); connect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack); ruler()->SetPlaybackCache(n->video_frame_cache()); @@ -207,7 +201,8 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) waveform_view_->ConnectTimelinePoints(GetConnectedTimelinePoints()); } - UpdateRendererParameters(); + UpdateRendererVideoParameters(); + UpdateRendererAudioParameters(); // Set texture to new texture (or null if no viewer node is available) ForceUpdate(); @@ -217,18 +212,12 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) { PauseInternal(); - if (cache_background_task_ == our_cache_background_task_) { - StopAllBackgroundCacheTasks(true); - cache_background_task_ = nullptr; - } - cache_wait_timer_.stop(); - disconnect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot); disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); - disconnect(n, &ViewerOutput::ParamsChanged, this, &ViewerWidget::UpdateRendererParameters); + disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters); + disconnect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); disconnect(n->video_frame_cache(), &FrameHashCache::Shifted, this, &ViewerWidget::ViewerShiftedRange); - disconnect(n->audio_playback_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedRange); disconnect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack); ruler()->SetPlaybackCache(nullptr); @@ -261,23 +250,9 @@ void ViewerWidget::resizeEvent(QResizeEvent *event) { TimeBasedWidget::resizeEvent(event); - /* - int new_div = CalculateDivider(); - if (new_div != divider_) { - divider_ = new_div; - - UpdateRendererParameters(); - } - */ - UpdateMinimumScale(); } -ViewerDisplayWidget *ViewerWidget::display_widget() const -{ - return display_widget_; -} - void ViewerWidget::TogglePlayPause() { if (IsPlaying()) { @@ -359,13 +334,23 @@ void ViewerWidget::ForceUpdate() void ViewerWidget::SetAutoCacheEnabled(bool e) { - autocache_ = e; + renderer_->SetAutoCachePaused(!e); +} - if (autocache_) { - StartBackgroundCaching(); - } else if (cache_background_task_ == our_cache_background_task_) { - StopAllBackgroundCacheTasks(false); - cache_background_task_ = nullptr; +void ViewerWidget::CacheEntireSequence() +{ + renderer_->AutoCacheRange(TimeRange(rational(), GetConnectedNode()->video_frame_cache()->GetLength())); +} + +void ViewerWidget::CacheSequenceInOut() +{ + if (GetConnectedTimelinePoints() && GetConnectedTimelinePoints()->workarea()->enabled()) { + renderer_->AutoCacheRange(GetConnectedTimelinePoints()->workarea()->range()); + } else { + QMessageBox::warning(this, + tr("Error"), + tr("No in or out points are set to cache."), + QMessageBox::Ok); } } @@ -375,23 +360,6 @@ void ViewerWidget::SetGizmos(Node *node) display_widget_->SetGizmos(node); } -void ViewerWidget::StopAllBackgroundCacheTasks(bool wait) -{ - if (cache_background_task_) { - if (wait) { - TaskManager::instance()->CancelTaskAndWait(cache_background_task_); - } else { - cache_background_task_->Cancel(); - } - cache_background_task_ = nullptr; - } -} - -void ViewerWidget::SetBackgroundCacheTask(CacheTask *t) -{ - cache_background_task_ = t; -} - FramePtr DecodeCachedImage(const QString &fn, const rational& time) { FramePtr frame = FrameHashCache::LoadCacheFrame(fn); @@ -497,14 +465,13 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) } } - if (!busy_) { - busy_ = true; - busy_viewers_++; + // Kindly tell all viewers to stop caching + if (pause_autocache_during_playback_) { + foreach (ViewerWidget* viewer, instances_) { + viewer->renderer_->ClearVideoQueue(); + } } - StopAllBackgroundCacheTasks(false); - cache_wait_timer_.stop(); - if (!prequeuing_) { FinishPlayPreprocess(); } @@ -554,20 +521,6 @@ void ViewerWidget::PushScrubbedAudio() } } -/* -int ViewerWidget::CalculateDivider() -{ - if (GetConnectedNode() && Config::Current()["AutoSelectDivider"].toBool()) { - int long_side_of_video = qMax(GetConnectedNode()->video_params().width(), GetConnectedNode()->video_params().height()); - int long_side_of_widget = qMax(display_widget_->width(), display_widget_->height()); - - return qMax(1, int(qPow(2, qFloor(log2(double(long_side_of_video) / double(long_side_of_widget)))))); - } - - return divider_; -} -*/ - void ViewerWidget::UpdateMinimumScale() { if (!GetConnectedNode()) { @@ -638,22 +591,23 @@ PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool clear_render_queue) { QByteArray cached_hash = GetConnectedNode()->video_frame_cache()->GetHash(t); - if (cached_hash.isEmpty()) { + + QString cache_fn = GetConnectedNode()->video_frame_cache()->CachePathName(cached_hash); + + if (cached_hash.isEmpty() || !QFileInfo::exists(cache_fn)) { // Frame hasn't been cached, start render job if (clear_render_queue) { renderer_->ClearVideoQueue(); } - return renderer_->RenderFrame(t); + return renderer_->RenderFrame(t, true); } else { // Frame has been cached, grab the frame - QString cache_fn = GetConnectedNode()->video_frame_cache()->CachePathName(cached_hash); - RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeVideo, QVariant::fromValue(t)); QtConcurrent::run(DecodeCachedImage, ticket, cache_fn, t); - return ticket; + return ticket; } } @@ -785,7 +739,7 @@ void ViewerWidget::RendererGeneratedFrame() } } - ticket->deleteLater(); + delete ticket; } void ViewerWidget::RendererGeneratedFrameForQueue() @@ -810,73 +764,7 @@ void ViewerWidget::RendererGeneratedFrameForQueue() } } - watcher->deleteLater(); -} - -//#define PRINT_INVALID_RANGES -void ViewerWidget::StartBackgroundCaching() -{ - if (busy_) { - busy_viewers_--; - busy_ = false; - } - -#ifdef PRINT_INVALID_RANGES - if (GetConnectedNode()->video_frame_cache()->HasInvalidatedRanges()) { - qDebug() << "Video invalid:"; - foreach (const TimeRange& r, GetConnectedNode()->video_frame_cache()->GetInvalidatedRanges()) { - qDebug() << " " << r; - } - } - - if (GetConnectedNode()->audio_playback_cache()->HasInvalidatedRanges()) { - qDebug() << "Audio invalid:"; - foreach (const TimeRange& r, GetConnectedNode()->audio_playback_cache()->GetInvalidatedRanges()) { - qDebug() << " " << r; - } - } -#endif - - if (autocache_ - && GetConnectedNode() - && (GetConnectedNode()->video_frame_cache()->HasInvalidatedRanges() - || GetConnectedNode()->audio_playback_cache()->HasInvalidatedRanges())) { - if (cache_background_task_ || busy_viewers_) { - - // 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(renderer_, false); - - our_cache_background_task_ = cache_background_task_; - - TaskManager::instance()->AddTask(cache_background_task_); - } - } -} - -void ViewerWidget::BackgroundCacheFinished(Task* t) -{ - if (cache_background_task_ == t) { - cache_background_task_ = nullptr; - } -} - -void ViewerWidget::UpdateRendererParameters() -{ - if (cache_background_task_ == our_cache_background_task_) { - StopAllBackgroundCacheTasks(false); - } - - GetConnectedNode()->video_frame_cache()->InvalidateAll(); - GetConnectedNode()->audio_playback_cache()->InvalidateAll(); - - renderer_->SetVideoParams(GetConnectedNode()->video_params()); - renderer_->SetAudioParams(GetConnectedNode()->audio_params()); - - display_widget_->SetVideoParams(GetConnectedNode()->video_params()); + delete watcher; } void ViewerWidget::ShowContextMenu(const QPoint &pos) @@ -955,11 +843,34 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) menu.addSeparator(); { + Menu* cache_menu = new Menu(tr("Cache"), &menu); + menu.addMenu(cache_menu); + // Auto-cache - QAction* autocache_action = menu.addAction(tr("Auto-Cache")); + QAction* autocache_action = cache_menu->addAction(tr("Auto-Cache")); autocache_action->setCheckable(true); - autocache_action->setChecked(autocache_); + autocache_action->setChecked(!renderer_->IsAutoCachePaused()); connect(autocache_action, &QAction::triggered, this, &ViewerWidget::SetAutoCacheEnabled); + + cache_menu->addSeparator(); + + // Stop auto-cache while playing + QAction* pause_autocache_while_playing = cache_menu->addAction(tr("Pause Auto-Cache During Playback")); + pause_autocache_while_playing->setCheckable(true); + pause_autocache_while_playing->setChecked(pause_autocache_during_playback_); + connect(pause_autocache_while_playing, &QAction::triggered, this, [this](bool e){ + pause_autocache_during_playback_ = e; + }); + + cache_menu->addSeparator(); + + // Cache Entire Sequence + QAction* cache_entire_sequence = cache_menu->addAction(tr("Cache Entire Sequence")); + connect(cache_entire_sequence, &QAction::triggered, this, &ViewerWidget::CacheEntireSequence); + + // Cache In/Out Sequence + QAction* cache_inout_sequence = cache_menu->addAction(tr("Cache Sequence In/Out")); + connect(cache_inout_sequence, &QAction::triggered, this, &ViewerWidget::CacheSequenceInOut); } menu.addSeparator(); @@ -992,7 +903,7 @@ void ViewerWidget::Play(bool in_to_out_only) { if (in_to_out_only) { if (GetConnectedTimelinePoints() - && GetConnectedTimelinePoints()->workarea()->enabled()) { + && GetConnectedTimelinePoints()->workarea()->enabled()) { // Jump to in point SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedTimelinePoints()->workarea()->in(), timebase())); } else { @@ -1012,7 +923,7 @@ void ViewerWidget::Pause() { PauseInternal(); - StartBackgroundCaching(); + renderer_->SetAutoCachePlayhead(GetTime()); } void ViewerWidget::ShuttleLeft() @@ -1172,6 +1083,26 @@ void ViewerWidget::LengthChangedSlot(const rational &length) } } +void ViewerWidget::UpdateRendererVideoParameters() +{ + renderer_->ClearVideoQueue(); + + renderer_->SetVideoParams(GetConnectedNode()->video_params()); + + GetConnectedNode()->video_frame_cache()->InvalidateAll(); + + display_widget_->SetVideoParams(GetConnectedNode()->video_params()); +} + +void ViewerWidget::UpdateRendererAudioParameters() +{ + renderer_->ClearAudioQueue(); + + renderer_->SetAudioParams(GetConnectedNode()->audio_params()); + + GetConnectedNode()->audio_playback_cache()->InvalidateAll(); +} + void ViewerWidget::SetZoomFromMenu(QAction *action) { sizer_->SetZoom(action->data().toInt()); @@ -1179,22 +1110,10 @@ void ViewerWidget::SetZoomFromMenu(QAction *action) void ViewerWidget::ViewerInvalidatedVideoRange(const TimeRange &range) { + // If our current frame is within this range, we need to update if (GetTime() >= range.in() && (GetTime() < range.out() || range.in() == range.out())) { QMetaObject::invokeMethod(this, "ForceUpdate", Qt::QueuedConnection); } - - ViewerInvalidatedRange(); -} - -void ViewerWidget::ViewerInvalidatedRange() -{ - // Restart the cache wait timer - cache_wait_timer_.stop(); - StopAllBackgroundCacheTasks(false); - - if (!(qApp->mouseButtons() & Qt::LeftButton)) { - cache_wait_timer_.start(); - } } void ViewerWidget::ViewerShiftedRange(const rational &from, const rational &to) diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 64f892611..a1a45e0fc 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -97,9 +97,6 @@ public: void SetGizmos(Node* node); - static void StopAllBackgroundCacheTasks(bool wait); - static void SetBackgroundCacheTask(CacheTask* t); - public slots: void Play(bool in_to_out_only); @@ -124,6 +121,10 @@ public slots: void SetAutoCacheEnabled(bool e); + void CacheEntireSequence(); + + void CacheSequenceInOut(); + signals: /** * @brief Wrapper for ViewerGLWidget::CursorColor() @@ -166,7 +167,10 @@ protected: PlaybackControls* controls_; - ViewerDisplayWidget* display_widget() const; + ViewerDisplayWidget* display_widget() const + { + return display_widget_; + } private: void UpdateTimeInternal(int64_t i); @@ -179,8 +183,6 @@ private: void PushScrubbedAudio(); - int CalculateDivider(); - void UpdateMinimumScale(); void SetColorTransform(const ColorTransform& transform, ViewerDisplayWidget* sender); @@ -221,6 +223,8 @@ private: bool play_in_to_out_only_; + bool pause_autocache_during_playback_; + AudioWaveformView* waveform_view_; QList windows_; @@ -241,21 +245,11 @@ private: QList nonqueue_watchers_; - QTimer cache_wait_timer_; - - bool busy_; - - CacheTask* our_cache_background_task_; - rational last_length_; int prequeue_length_; - bool autocache_; - - static CacheTask* cache_background_task_; - - static int busy_viewers_; + static QVector instances_; private slots: void PlaybackTimerUpdate(); @@ -264,16 +258,14 @@ private slots: void LengthChangedSlot(const rational& length); - void UpdateRendererParameters(); + void UpdateRendererVideoParameters(); + + void UpdateRendererAudioParameters(); void ShowContextMenu(const QPoint& pos); void SetZoomFromMenu(QAction* action); - void ViewerInvalidatedVideoRange(const OLIVE_NAMESPACE::TimeRange &range); - - void ViewerInvalidatedRange(); - void ViewerShiftedRange(const OLIVE_NAMESPACE::rational& from, const OLIVE_NAMESPACE::rational& to); void UpdateStack(); @@ -294,9 +286,7 @@ private slots: void RendererGeneratedFrameForQueue(); - void StartBackgroundCaching(); - - void BackgroundCacheFinished(Task *t); + void ViewerInvalidatedVideoRange(const OLIVE_NAMESPACE::TimeRange &range); };