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.
This commit is contained in:
+22
-13
@@ -1267,23 +1267,32 @@ void Core::CacheActiveSequence(bool in_out_only)
|
||||
TimeBasedPanel* p = PanelManager::instance()->MostRecentlyFocused<TimeBasedPanel>();
|
||||
|
||||
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<ViewerPanel*> all_viewers = PanelManager::instance()->GetPanelsOfType<ViewerPanel>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -90,6 +90,16 @@ void ViewerPanelBase::SetGizmos(Node *node)
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->SetGizmos(node);
|
||||
}
|
||||
|
||||
void ViewerPanelBase::CacheEntireSequence()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->CacheEntireSequence();
|
||||
}
|
||||
|
||||
void ViewerPanelBase::CacheSequenceInOut()
|
||||
{
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->CacheSequenceInOut();
|
||||
}
|
||||
|
||||
void ViewerPanelBase::CreateScopePanel(ScopePanel::Type type)
|
||||
{
|
||||
ViewerWidget* vw = static_cast<ViewerWidget*>(GetTimeBasedWidget());
|
||||
|
||||
@@ -57,6 +57,10 @@ public:
|
||||
public slots:
|
||||
void SetGizmos(Node* node);
|
||||
|
||||
void CacheEntireSequence();
|
||||
|
||||
void CacheSequenceInOut();
|
||||
|
||||
protected:
|
||||
void CreateScopePanel(ScopePanel::Type type);
|
||||
|
||||
|
||||
@@ -31,13 +31,27 @@
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
QVector<RenderBackend*> 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<ViewerOutput*>(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<rational> ×)
|
||||
RenderTicketPtr RenderBackend::Hash(const QVector<rational> ×, bool prioritize)
|
||||
{
|
||||
if (!viewer_node_) {
|
||||
return nullptr;
|
||||
}
|
||||
Q_ASSERT(viewer_node_);
|
||||
|
||||
SetActiveInstance();
|
||||
|
||||
RenderTicketPtr ticket = std::make_shared<RenderTicket>(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>(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>(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<TimeRange> 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;i<workers_.size();i++) {
|
||||
RenderWorker* worker = CreateNewWorker();
|
||||
@@ -323,9 +401,14 @@ void RenderBackend::RunNextJob()
|
||||
RenderTicketPtr ticket = render_queue_.front();
|
||||
render_queue_.pop_front();
|
||||
|
||||
running_tickets_.push_back(ticket);
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &RenderBackend::TicketFinished);
|
||||
watcher->SetTicket(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<QVector<rational> >());
|
||||
break;
|
||||
case RenderTicket::kTypeVideo:
|
||||
QtConcurrent::run(&pool_,
|
||||
{
|
||||
rational frame = ticket->GetTime().value<rational>();
|
||||
|
||||
QtConcurrent::run(&thread_pool_,
|
||||
worker,
|
||||
&RenderWorker::RenderFrame,
|
||||
ticket,
|
||||
copied_viewer_node_,
|
||||
ticket->GetTime().value<rational>());
|
||||
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<RenderTicketWatcher*>(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<rational> 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<rational>& times, const QVector<QByteArray>& hashes, qint64 job_time)
|
||||
{
|
||||
std::vector<QByteArray> existing_hashes;
|
||||
|
||||
for (int i=0; i<times.size(); i++) {
|
||||
// See if hash already exists in disk cache
|
||||
const QByteArray& hash = hashes.at(i);
|
||||
const rational& time = times.at(i);
|
||||
|
||||
// Check memory list since disk checking is slow
|
||||
bool hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), hash) != existing_hashes.end());
|
||||
|
||||
if (!hash_exists) {
|
||||
hash_exists = QFileInfo::exists(cache->CachePathName(hash));
|
||||
|
||||
if (hash_exists) {
|
||||
existing_hashes.push_back(hash);
|
||||
}
|
||||
}
|
||||
|
||||
cache->SetHash(time, hash, job_time, hash_exists);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::AutoCacheHashesGenerated()
|
||||
{
|
||||
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
|
||||
|
||||
if (autocache_hash_tasks_.contains(watcher)) {
|
||||
if (!watcher->WasCancelled()) {
|
||||
const HashJobInfo& info = autocache_hash_tasks_.value(watcher);
|
||||
|
||||
QFutureWatcher<void>* hw = new QFutureWatcher<void>();
|
||||
connect(hw, &QFutureWatcher<void>::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<QVector<QByteArray> >(),
|
||||
info.job_time));
|
||||
}
|
||||
|
||||
autocache_hash_tasks_.remove(watcher);
|
||||
}
|
||||
|
||||
delete watcher;
|
||||
}
|
||||
|
||||
void RenderBackend::AutoCacheHashesProcessed()
|
||||
{
|
||||
QFutureWatcher<void>* watcher = static_cast<QFutureWatcher<void>*>(sender());
|
||||
|
||||
if (autocache_hash_process_tasks_.contains(watcher)) {
|
||||
autocache_hash_process_tasks_.removeOne(watcher);
|
||||
|
||||
AutoCacheRequeueFrames();
|
||||
}
|
||||
|
||||
delete watcher;
|
||||
}
|
||||
|
||||
void RenderBackend::AutoCacheAudioRendered()
|
||||
{
|
||||
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(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<SampleBufferPtr>(),
|
||||
job_info.job_time);
|
||||
}
|
||||
|
||||
autocache_audio_tasks_.remove(watcher);
|
||||
}
|
||||
|
||||
delete watcher;
|
||||
}
|
||||
|
||||
void RenderBackend::AutoCacheVideoRendered()
|
||||
{
|
||||
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
|
||||
|
||||
if (autocache_video_tasks_.contains(watcher)) {
|
||||
if (!watcher->WasCancelled()) {
|
||||
const VideoJobInfo& info = autocache_video_tasks_.value(watcher);
|
||||
|
||||
// Download frame in another thread
|
||||
QFutureWatcher<bool>* w = new QFutureWatcher<bool>();
|
||||
autocache_video_download_tasks_.insert(w, info);
|
||||
connect(w, &QFutureWatcher<bool>::finished, this, &RenderBackend::AutoCacheVideoDownloaded);
|
||||
w->setFuture(QtConcurrent::run(FrameHashCache::SaveCacheFrame,
|
||||
info.hash,
|
||||
watcher->Get().value<FramePtr>()));
|
||||
}
|
||||
|
||||
autocache_video_tasks_.remove(watcher);
|
||||
}
|
||||
|
||||
delete watcher;
|
||||
}
|
||||
|
||||
void RenderBackend::AutoCacheVideoDownloaded()
|
||||
{
|
||||
QFutureWatcher<bool>* watcher = static_cast<QFutureWatcher<bool>*>(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<RenderTicketPtr>::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<rational> invalidated_ranges = viewer_node_->video_frame_cache()->GetInvalidatedFrames(using_range);
|
||||
|
||||
ClearVideoQueue();
|
||||
|
||||
// QMaps are automatically sorted by time which is always best for rendering
|
||||
QList<QByteArray> 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
|
||||
|
||||
@@ -23,12 +23,14 @@
|
||||
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#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<rational>(),
|
||||
playhead + Config::Current()["DiskCacheAhead"].value<rational>());
|
||||
|
||||
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<rational> ×);
|
||||
RenderTicketPtr Hash(const QVector<rational> ×, 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<rational>& times, const QVector<QByteArray>& hashes, qint64 job_time);
|
||||
|
||||
ViewerOutput* viewer_node_;
|
||||
|
||||
// VIDEO MEMBERS
|
||||
@@ -126,10 +171,10 @@ private:
|
||||
QHash<Node*, Node*> copy_map_;
|
||||
ViewerOutput* copied_viewer_node_;
|
||||
|
||||
QThreadPool pool_;
|
||||
|
||||
std::list<RenderTicketPtr> render_queue_;
|
||||
|
||||
std::list<RenderTicketPtr> running_tickets_;
|
||||
|
||||
struct WorkerData {
|
||||
RenderWorker* worker;
|
||||
bool busy;
|
||||
@@ -137,17 +182,74 @@ private:
|
||||
|
||||
QVector<WorkerData> 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<RenderBackend*> instances_;
|
||||
static QMutex instance_lock_;
|
||||
static RenderBackend* active_instance_;
|
||||
static QThreadPool thread_pool_;
|
||||
void SetActiveInstance();
|
||||
|
||||
struct HashJobInfo {
|
||||
QVector<rational> times;
|
||||
qint64 job_time;
|
||||
};
|
||||
|
||||
struct AudioJobInfo {
|
||||
TimeRange range;
|
||||
qint64 job_time;
|
||||
};
|
||||
|
||||
struct VideoJobInfo {
|
||||
QByteArray hash;
|
||||
qint64 job_time;
|
||||
};
|
||||
|
||||
QMap<RenderTicketWatcher*, HashJobInfo> autocache_hash_tasks_;
|
||||
|
||||
QList<QFutureWatcher<void>*> autocache_hash_process_tasks_;
|
||||
|
||||
QMap<RenderTicketWatcher*, AudioJobInfo> autocache_audio_tasks_;
|
||||
|
||||
QMap<RenderTicketWatcher*, VideoJobInfo> autocache_video_tasks_;
|
||||
|
||||
QMap<QFutureWatcher<bool>*, VideoJobInfo> autocache_video_download_tasks_;
|
||||
|
||||
QVector<QByteArray> 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
|
||||
|
||||
@@ -31,6 +31,11 @@ class RenderTicketWatcher : public QObject
|
||||
public:
|
||||
RenderTicketWatcher(QObject* parent = nullptr);
|
||||
|
||||
RenderTicketPtr GetTicket() const
|
||||
{
|
||||
return ticket_;
|
||||
}
|
||||
|
||||
void SetTicket(RenderTicketPtr ticket);
|
||||
|
||||
bool WasCancelled();
|
||||
|
||||
@@ -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<rational, QByteArray>::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<rational> FrameHashCache::GetFramesWithHash(const QByteArray &hash)
|
||||
{
|
||||
QMutexLocker locker(lock());
|
||||
@@ -175,10 +206,17 @@ QVector<rational> FrameHashCache::GetInvalidatedFrames()
|
||||
{
|
||||
QMutexLocker locker(lock());
|
||||
|
||||
return GetFrameListFromTimeRange(NoLockGetInvalidatedRanges());
|
||||
return GetFrameListFromTimeRange(NoLockGetInvalidatedRanges(), timebase_);
|
||||
}
|
||||
|
||||
void FrameHashCache::SaveCacheFrame(const QByteArray& hash,
|
||||
QVector<rational> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<rational> GetFrameListFromTimeRange(TimeRangeList range_list, const rational& timebase);
|
||||
QVector<rational> GetFrameListFromTimeRange(const TimeRangeList &range);
|
||||
QVector<rational> GetInvalidatedFrames();
|
||||
QVector<rational> 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;
|
||||
|
||||
Vendored
+1
-1
@@ -72,7 +72,7 @@ QFuture<void> CacheTask::DownloadFrame(FramePtr frame, const QByteArray &hash)
|
||||
void CacheTask::FrameDownloaded(const QByteArray &hash, const std::list<rational> ×)
|
||||
{
|
||||
foreach (const rational& t, times) {
|
||||
viewer()->video_frame_cache()->SetHash(t, hash, job_time());
|
||||
viewer()->video_frame_cache()->SetHash(t, hash, job_time(), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<FramePtr>();
|
||||
if (!i->frame_future->WasCancelled()) {
|
||||
FramePtr f = i->frame_future->Get().value<FramePtr>();
|
||||
|
||||
// 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 {
|
||||
|
||||
+95
-176
@@ -44,12 +44,10 @@
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
QVector<ViewerWidget*> 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>(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)
|
||||
|
||||
+15
-25
@@ -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<ViewerWindow*> windows_;
|
||||
@@ -241,21 +245,11 @@ private:
|
||||
|
||||
QList<RenderTicketWatcher*> 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<ViewerWidget*> 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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user