diff --git a/CMakeLists.txt b/CMakeLists.txt index ee3cacf7e..95a3ef62d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ find_package(Qt5 5.6 REQUIRED OpenGL Svg LinguistTools + Concurrent ) find_package(FFMPEG 3.0 REQUIRED diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 4998e2a99..6050fabfe 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -138,6 +138,7 @@ target_link_libraries( Qt5::Multimedia Qt5::OpenGL Qt5::Svg + Qt5::Concurrent OpenGL::GL FFMPEG::avutil FFMPEG::avcodec diff --git a/app/config/config.cpp b/app/config/config.cpp index 479f5b923..cd0b7edc0 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -83,7 +83,7 @@ void Config::SetDefaults() config_map_["DropWithoutSequenceBehavior"] = TimelineWidget::kDWSAsk; config_map_["Loop"] = false; - config_map_["AutoCache"] = true; + config_map_["AutoCache"] = true;//false;//true; config_map_["AutoCacheInterval"] = 1000; config_map_["NodeCatColor0"] = QVariant::fromValue(Color(0.75f, 0.75f, 0.75f)); diff --git a/app/render/backend/CMakeLists.txt b/app/render/backend/CMakeLists.txt index b929d7a74..79650bd7b 100644 --- a/app/render/backend/CMakeLists.txt +++ b/app/render/backend/CMakeLists.txt @@ -18,15 +18,15 @@ add_subdirectory(opengl) set(OLIVE_SOURCES ${OLIVE_SOURCES} - - render/backend/renderbackend.h - render/backend/renderbackend.cpp - - render/backend/renderworker.h - render/backend/renderworker.cpp - render/backend/colorprocessorcache.h render/backend/decodercache.h - + render/backend/renderbackend.h + render/backend/renderbackend.cpp + render/backend/renderticket.h + render/backend/renderticket.cpp + render/backend/renderticketwatcher.h + render/backend/renderticketwatcher.cpp + render/backend/renderworker.h + render/backend/renderworker.cpp PARENT_SCOPE ) diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index de5254e54..26d575f25 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -34,11 +34,8 @@ OLIVE_NAMESPACE_ENTER RenderBackend::RenderBackend(QObject *parent) : QObject(parent), viewer_node_(nullptr), - auto_audio_(true), - ic_from_conform_(false) + update_with_graph_(false) { - // FIXME: Don't create in CLI mode - //cancel_dialog_ = new RenderCancelDialog(Core::instance()->main_window()); } RenderBackend::~RenderBackend() @@ -53,92 +50,112 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) } if (viewer_node_) { - // Clear queue and wait for any currently running actions to complete - CancelQueue(); - // Delete all of our copied nodes - hash_pool_.Close(); - video_pool_.Close(); - audio_pool_.Close(); - queued_audio_.clear(); + pool_.clear(); + pool_.waitForDone(); + + // Cancel all tickets + foreach (RenderTicketPtr t, render_queue_) { + t->Cancel(); + } + render_queue_.clear(); + + // Delete all the nodes + qDeleteAll(copy_map_); + copy_map_.clear(); + copied_viewer_node_ = nullptr; disconnect(viewer_node_, &ViewerOutput::GraphChangedFrom, this, &RenderBackend::NodeGraphChanged); - - disconnect(viewer_node_->audio_playback_cache(), - &AudioPlaybackCache::Invalidated, - this, - &RenderBackend::AudioInvalidated); } // Set viewer node viewer_node_ = viewer_node; if (viewer_node_) { - // Initiate instances with new node - hash_pool_.Init(viewer_node_); - video_pool_.Init(viewer_node_); - audio_pool_.Init(viewer_node_); + // Copy graph + copied_viewer_node_ = static_cast(viewer_node_->copy()); + copy_map_.insert(viewer_node_, copied_viewer_node_); - connect(viewer_node_, - &ViewerOutput::GraphChangedFrom, - this, - &RenderBackend::NodeGraphChanged); + NodeGraphChanged(viewer_node_->texture_input()); + NodeGraphChanged(viewer_node_->samples_input()); + ProcessUpdateQueue(); - if (auto_audio_) { - // Listen for audio invalidation signals - connect(viewer_node_->audio_playback_cache(), - &AudioPlaybackCache::Invalidated, + if (update_with_graph_) { + connect(viewer_node_, + &ViewerOutput::GraphChangedFrom, this, - &RenderBackend::AudioInvalidated); - - // Start caching audio - foreach (const TimeRange& r, viewer_node_->audio_playback_cache()->GetInvalidatedRanges()) { - AudioInvalidated(r); - } + &RenderBackend::NodeGraphChanged); } } } -void RenderBackend::CancelQueue() +void RenderBackend::SetUpdateWithGraph(bool e) { - // FIXME: Implement something better than this... - video_pool_.threads.waitForDone(); - audio_pool_.threads.waitForDone(); - hash_pool_.threads.waitForDone(); + update_with_graph_ = e; } -QFuture RenderBackend::Hash(const rational &time, bool block_for_update) +void RenderBackend::ClearVideoQueue() { - return QtConcurrent::run(&hash_pool_.threads, - GetInstanceFromPool(hash_pool_), - &RenderWorker::Hash, - time, - block_for_update); + foreach (RenderTicketPtr t, render_queue_) { + t->Cancel(); + } + render_queue_.clear(); } -QFuture RenderBackend::RenderFrame(const rational &time, bool clear_queue, bool block_for_update) +QFuture > RenderBackend::Hash(const QList ×) { - if (clear_queue) { - video_pool_.threads.clear(); + return QtConcurrent::run([this](const QList ×){ + QList hashes; + + foreach (const rational& t, times) { + QCryptographicHash hasher(QCryptographicHash::Sha1); + + // Embed video parameters into this hash + hasher.addData(reinterpret_cast(&video_params_.effective_width()), sizeof(int)); + hasher.addData(reinterpret_cast(&video_params_.effective_height()), sizeof(int)); + hasher.addData(reinterpret_cast(&video_params_.format()), sizeof(PixelFormat::Format)); + hasher.addData(reinterpret_cast(&video_params_.mode()), sizeof(RenderMode::Mode)); + + copied_viewer_node_->Hash(hasher, t); + + hashes.append(hasher.result()); + } + + return hashes; + }, times); +} + +RenderTicketPtr RenderBackend::RenderFrame(const rational &time) +{ + if (!viewer_node_) { + return nullptr; } - return QtConcurrent::run(&video_pool_.threads, - GetInstanceFromPool(video_pool_), - &RenderWorker::RenderFrame, - time, - block_for_update); + RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeVideo, + TimeRange(time, time)); + + render_queue_.append(ticket); + + RunNextJob(); + + return ticket; } -QFuture RenderBackend::RenderAudio(const TimeRange &r, bool block_for_update) +RenderTicketPtr RenderBackend::RenderAudio(const TimeRange &r) { - return QtConcurrent::run(&audio_pool_.threads, - GetInstanceFromPool(audio_pool_), - &RenderWorker::RenderAudio, - r, - block_for_update); + if (!viewer_node_) { + return nullptr; + } + + RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeAudio, + r); + + render_queue_.append(ticket); + + return ticket; } void RenderBackend::SetVideoParams(const VideoRenderingParams ¶ms) @@ -156,18 +173,6 @@ void RenderBackend::SetVideoDownloadMatrix(const QMatrix4x4 &mat) video_download_matrix_ = mat; } -void RenderBackend::SetAutomaticAudio(bool e) -{ - auto_audio_ = e; -} - -void RenderBackend::WorkerStartedRenderingAudio(const TimeRange &r) -{ - queued_audio_lock_.lock(); - queued_audio_.RemoveTimeRange(r); - queued_audio_lock_.unlock(); -} - QList RenderBackend::SplitRangeIntoChunks(const TimeRange &r) { const int chunk_size = 2; @@ -179,7 +184,7 @@ QList RenderBackend::SplitRangeIntoChunks(const TimeRange &r) for (int i=start_time; i RenderBackend::SplitRangeIntoChunks(const TimeRange &r) void RenderBackend::NodeGraphChanged(NodeInput *source) { - video_pool_.Queue(source); - audio_pool_.Queue(source); - hash_pool_.Queue(source); -} + if (!graph_update_queue_.isEmpty()) { + // First, check if anything in our queue is a dependency of this input. If so, we should remove + // it and just update this input. -void RenderBackend::UpdateInstance(RenderWorker *instance) -{ - instance->SetAvailable(false); - instance->ProcessQueue(); - instance->SetVideoParams(video_params_); - instance->SetAudioParams(audio_params_); - instance->SetVideoDownloadMatrix(video_download_matrix_); - instance->SetAudioModeIsPreview(auto_audio_); + // First we need to find our copy of the input being queued + Node* our_copy_node = copy_map_.value(source->parentNode()); + + if (our_copy_node) { + NodeInput* our_copy = our_copy_node->GetInputWithID(source->id()); + QList our_copy_deps = our_copy->GetDependencies(our_copy); + + for (int i=0;iparentNode()); + + // If this input isn't connected anymore, it obviously won't come up as a dependency + if (our_copy_deps.contains(check_input_our_copy)) { + graph_update_queue_.removeAt(i); + i--; + } + } + } + } + + graph_update_queue_.append(source); } void RenderBackend::Close() { - video_pool_.threads.clear(); - audio_pool_.threads.clear(); - hash_pool_.threads.clear(); + SetViewerNode(nullptr); - CancelQueue(); - - video_pool_.Destroy(); - audio_pool_.Destroy(); - hash_pool_.Destroy(); + for (int i=0;ideleteLater(); + } + workers_.clear(); } -RenderWorker *RenderBackend::GetInstanceFromPool(RenderPool& pool) +void RenderBackend::RunNextJob() { - RenderWorker* instance = nullptr; - - foreach (RenderWorker* worker, pool.instances) { - if (worker->IsAvailable()) { - instance = worker; - break; - } - } - - if (!instance) { - if (pool.instances.size() < pool.threads.maxThreadCount()) { - // Can create another instance - instance = CreateNewWorker(); - pool.instances.append(instance); - - if (viewer_node_) { - instance->Init(viewer_node_); - } - - connect(instance, &RenderWorker::FinishedJob, - this, &RenderBackend::WorkerFinished, Qt::QueuedConnection); - - connect(instance, &RenderWorker::AudioConformUnavailable, - this, &RenderBackend::AudioConformUnavailable, Qt::QueuedConnection); - } else { - instance = pool.instances.at(pool.queuer % pool.instances.size()); - pool.queuer++; - } - } - - return instance; -} - -void RenderBackend::ListenForConformSignal(AudioStreamPtr s) -{ - foreach (const ConformWaitInfo& info, conform_wait_info_) { - if (info.stream == s) { - // We've probably already connected to this one - return; - } - } - - connect(s.get(), &AudioStream::ConformAppended, this, &RenderBackend::AudioConformUpdated); -} - -void RenderBackend::StopListeningForConformSignal(AudioStream *s) -{ - foreach (const ConformWaitInfo& info, conform_wait_info_) { - if (info.stream.get() == s) { - // There are still conforms we're waiting for, don't disconnect - return; - } - } - - disconnect(s, &AudioStream::ConformAppended, this, &RenderBackend::AudioConformUpdated); -} - -void RenderBackend::AudioConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params) -{ - ConformWaitInfo info = {stream, params, range, stream_time}; - - if (conform_wait_info_.contains(info)) { + // If queue is empty, nothing to be done + if (render_queue_.isEmpty()) { return; } - AudioStreamPtr audio_stream = std::static_pointer_cast(stream); - - if (audio_stream->try_start_conforming(params)) { - - // Start indexing process - ListenForConformSignal(audio_stream); - - conform_wait_info_.append(info); - - ConformTask* conform_task = new ConformTask(audio_stream, params); - - TaskManager::instance()->AddTask(conform_task); - - } else if (audio_stream->has_conformed_version(params)) { - - // Conform JUST finished, requeue this time - ic_from_conform_ = true; - AudioInvalidated(range); - ic_from_conform_ = false; - - } else { - - // A conform task is already running, so we'll just wait for it - ListenForConformSignal(audio_stream); - - conform_wait_info_.append(info); - - } -} - -void RenderBackend::AudioConformUpdated(AudioRenderingParams params) -{ - AudioStream *stream = static_cast(sender()); - - for (int i=0;i r.in()) { - info.affected_range.set_out(r.in()); - } else if (info.affected_range.in() < r.out() && info.affected_range.out() > r.out()) { - info.affected_range.set_in(r.out()); + foreach (const WorkerData& data, workers_) { + if (data.busy) { + all_workers_available = false; + break; } } - } - // Split into 2 second chunks, one for each thread - QList split_ranges = SplitRangeIntoChunks(r); - - foreach (const TimeRange& this_range, split_ranges) { - - { - // Check if this range is already in the queue but hasn't started yet, in which case it'll - // automatically update to the parameters we have now anyway and we don't need to queue again - QMutexLocker locker(&queued_audio_lock_); - if (queued_audio_.ContainsTimeRange(this_range)) { - continue; - } - queued_audio_.InsertTimeRange(this_range); - } - - // Queue this range - QFutureWatcher* watcher = new QFutureWatcher(); - connect(watcher, &QFutureWatcher::finished, - this, &RenderBackend::AudioRendered); - - audio_jobs_.insert(watcher, this_range); - - watcher->setFuture(RenderAudio(this_range, auto_audio_)); - } -} - -void RenderBackend::AudioRendered() -{ - QFutureWatcher* watcher = static_cast*>(sender()); - - if (audio_jobs_.contains(watcher)) { - TimeRange r = audio_jobs_.take(watcher); - - if (watcher->result()) { - viewer_node_->audio_playback_cache()->WritePCM(r, watcher->result()); + if (all_workers_available) { + // Process queue + ProcessUpdateQueue(); } else { - viewer_node_->audio_playback_cache()->WriteSilence(r); + return; } } - watcher->deleteLater(); + // If we have no workers allocated, allocate them now + if (workers_.isEmpty()) { + // Allocate workers here + workers_.resize(pool_.maxThreadCount()); + + for (int i=0;iSetVideoParams(video_params_); + worker->SetAudioParams(audio_params_); + worker->SetVideoDownloadMatrix(video_download_matrix_); + worker->SetCopyMap(©_map_); + + RenderTicketPtr ticket = render_queue_.takeFirst(); + + switch (ticket->GetType()) { + case RenderTicket::kTypeVideo: + QtConcurrent::run(worker, + &RenderWorker::RenderFrame, + ticket, + copied_viewer_node_, + ticket->GetTime().in()); + break; + case RenderTicket::kTypeAudio: + QtConcurrent::run(worker, + &RenderWorker::RenderAudio, + ticket, + copied_viewer_node_, + ticket->GetTime()); + break; + } + + if (render_queue_.isEmpty()) { + // No more jobs, can exit here + break; + } + } + } +} + +void RenderBackend::ProcessUpdateQueue() +{ + while (!graph_update_queue_.isEmpty()) { + CopyNodeInputValue(graph_update_queue_.takeFirst()); + } } void RenderBackend::WorkerFinished() { - static_cast(sender())->SetAvailable(true); + RenderWorker* worker = static_cast(sender()); + + // Set busy state to false + for (int i=0;iparentNode()); + NodeInput* our_copy = our_copy_node->GetInputWithID(input->id()); -RenderBackend::RenderPool::RenderPool() : - queuer(0) -{ -} + // Copy the standard/keyframe values between these two inputs + NodeInput::CopyValues(input, + our_copy, + false); -void RenderBackend::RenderPool::Init(ViewerOutput* v) -{ - foreach (RenderWorker* instance, instances) { - instance->Init(v); + // Handle connections + if (input->IsConnected() || our_copy->IsConnected()) { + // If one of the inputs is connected, it's likely this change came from connecting or + // disconnecting whatever was connected to it + + // We start by removing all old dependencies from the map + QList old_deps = our_copy->GetExclusiveDependencies(); + foreach (Node* i, old_deps) { + copy_map_.take(copy_map_.key(i))->deleteLater(); + } + + // And clear any other edges + while (!our_copy->edges().isEmpty()) { + NodeParam::DisconnectEdge(our_copy->edges().first()); + } + + // Then we copy all node dependencies and connections (if there are any) + CopyNodeMakeConnection(input, our_copy); + } + + // Call on sub-elements too + if (input->IsArray()) { + foreach (NodeInput* i, static_cast(input)->sub_params()) { + CopyNodeInputValue(i); + } } } -void RenderBackend::RenderPool::Queue(NodeInput *input) +Node* RenderBackend::CopyNodeConnections(Node* src_node) { - foreach (RenderWorker* worker, instances) { - worker->Queue(input); + // Check if this node is already in the map + Node* dst_node = copy_map_.value(src_node); + + // If not, create it now + if (!dst_node) { + dst_node = src_node->copy(); + + if (dst_node->IsTrack()) { + // Hack that ensures the track type is set since we don't bother copying the whole timeline + static_cast(dst_node)->set_track_type(static_cast(src_node)->track_type()); + } + + copy_map_.insert(src_node, dst_node); } + + // Make sure its values are copied + Node::CopyInputs(src_node, dst_node, false); + + // Copy all connections + QList src_node_inputs = src_node->GetInputsIncludingArrays(); + QList dst_node_inputs = dst_node->GetInputsIncludingArrays(); + + for (int i=0;iIsConnected()) { + Node* dst_node = CopyNodeConnections(src_input->get_connected_node()); -void RenderBackend::RenderPool::Close() -{ - foreach (RenderWorker* worker, instances) { - worker->Close(); + NodeOutput* corresponding_output = dst_node->GetOutputWithID(src_input->get_connected_output()->id()); + + NodeParam::ConnectEdge(corresponding_output, + dst_input); } } diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index 81568f9a0..0025662b2 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -28,6 +28,7 @@ #include "node/graph.h" #include "node/output/viewer/viewer.h" #include "render/backend/colorprocessorcache.h" +#include "renderticket.h" #include "renderworker.h" OLIVE_NAMESPACE_ENTER @@ -42,22 +43,26 @@ public: void SetViewerNode(ViewerOutput* viewer_node); - void CancelQueue(); + void SetUpdateWithGraph(bool e); + + void ClearVideoQueue(); /** * @brief Asynchronously generate a hash at a given time */ - QFuture Hash(const rational& time, bool block_for_update); + QFuture > Hash(const QList& times); /** * @brief Asynchronously generate a frame at a given time */ - QFuture RenderFrame(const rational& time, bool clear_queue, bool block_for_update); + RenderTicketPtr RenderFrame(const rational& time); + + QFuture RenderFrames(const QList& frames); /** * @brief Asynchronously generate a chunk of audio */ - QFuture RenderAudio(const TimeRange& r, bool block_for_update); + QFuture RenderAudio(const TimeRange& r); void SetVideoParams(const VideoRenderingParams& params); @@ -65,86 +70,52 @@ public: void SetVideoDownloadMatrix(const QMatrix4x4& mat); - void SetAutomaticAudio(bool e); - - void WorkerStartedRenderingAudio(const TimeRange& r); - static QList SplitRangeIntoChunks(const TimeRange& r); public slots: void NodeGraphChanged(NodeInput *source); - void UpdateInstance(OLIVE_NAMESPACE::RenderWorker* instance); - protected: virtual RenderWorker* CreateNewWorker() = 0; void Close(); private: - struct RenderPool { - RenderPool(); + void CopyNodeInputValue(NodeInput* input); + Node *CopyNodeConnections(Node *src_node); + void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input); - QVector instances; - int queuer; - QThreadPool threads; + void RunNextJob(); - void Init(ViewerOutput *v); - void Queue(NodeInput* input); - void Close(); - void Destroy(); - }; - - RenderWorker *GetInstanceFromPool(RenderPool &pool); + void ProcessUpdateQueue(); ViewerOutput* viewer_node_; - //RenderCancelDialog* cancel_dialog_; - - RenderPool video_pool_; - RenderPool audio_pool_; - RenderPool hash_pool_; - - QMutex queued_audio_lock_; - TimeRangeList queued_audio_; - - bool auto_audio_; - // VIDEO MEMBERS VideoRenderingParams video_params_; QMatrix4x4 video_download_matrix_; // AUDIO MEMBERS AudioRenderingParams audio_params_; - QHash< QFutureWatcher*, TimeRange > audio_jobs_; - struct ConformWaitInfo { - StreamPtr stream; - AudioRenderingParams params; - TimeRange affected_range; - rational stream_time; + QList graph_update_queue_; + QHash copy_map_; + ViewerOutput* copied_viewer_node_; - bool operator==(const ConformWaitInfo& rhs) const; + QThreadPool pool_; + + QLinkedList render_queue_; + + struct WorkerData { + RenderWorker* worker; + bool busy; }; - QList conform_wait_info_; + QVector workers_; - void ListenForConformSignal(AudioStreamPtr s); - - void StopListeningForConformSignal(AudioStream *s); - - bool ic_from_conform_; + bool update_with_graph_; private slots: - void AudioConformUnavailable(StreamPtr stream, TimeRange range, - rational stream_time, AudioRenderingParams params); - - void AudioConformUpdated(OLIVE_NAMESPACE::AudioRenderingParams params); - - void AudioInvalidated(const TimeRange &r); - - void AudioRendered(); - void WorkerFinished(); }; diff --git a/app/render/backend/renderticket.cpp b/app/render/backend/renderticket.cpp new file mode 100644 index 000000000..85d0d46d8 --- /dev/null +++ b/app/render/backend/renderticket.cpp @@ -0,0 +1,103 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "renderticket.h" + +OLIVE_NAMESPACE_ENTER + +RenderTicket::RenderTicket(Type type, const TimeRange &time) : + finished_(false), + cancelled_(false), + time_(time), + type_(type) +{ +} + +void RenderTicket::WaitForFinished() +{ + QMutexLocker locker(&lock_); + + if (!finished_) { + wait_.wait(&lock_); + } +} + +QVariant RenderTicket::Get() +{ + QMutexLocker locker(&lock_); + + if (!finished_) { + wait_.wait(&lock_); + } + + return result_; +} + +bool RenderTicket::IsFinished(bool lock) +{ + if (lock) { + lock_.lock(); + } + + bool finished = finished_; + + if (lock) { + lock_.unlock(); + } + + return finished; +} + +bool RenderTicket::WasCancelled() +{ + QMutexLocker locker(&lock_); + + return cancelled_; +} + +void RenderTicket::Finish(QVariant result) +{ + QMutexLocker locker(&lock_); + + finished_ = true; + result_ = result; + + wait_.wakeAll(); + + locker.unlock(); + + emit Finished(); +} + +void RenderTicket::Cancel() +{ + QMutexLocker locker(&lock_); + + finished_ = true; + cancelled_ = true; + + wait_.wakeAll(); + + locker.unlock(); + + emit Finished(); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderticket.h b/app/render/backend/renderticket.h new file mode 100644 index 000000000..573b5f259 --- /dev/null +++ b/app/render/backend/renderticket.h @@ -0,0 +1,94 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef RENDERTICKET_H +#define RENDERTICKET_H + +#include + +#include "codec/frame.h" +#include "codec/samplebuffer.h" +#include "common/timerange.h" + +OLIVE_NAMESPACE_ENTER + +class RenderTicket : public QObject +{ + Q_OBJECT +public: + enum Type { + kTypeVideo, + kTypeAudio + }; + + RenderTicket(Type type, const TimeRange& time); + + const TimeRange& GetTime() const + { + return time_; + } + + Type GetType() const + { + return type_; + } + + void WaitForFinished(); + + QVariant Get(); + + bool IsFinished(bool lock = true); + + bool WasCancelled(); + + QMutex* lock() + { + return &lock_; + } + + void Finish(QVariant result); + + void Cancel(); + +signals: + void Finished(); + +private: + bool finished_; + + bool cancelled_; + + QVariant result_; + + QMutex lock_; + + QWaitCondition wait_; + + TimeRange time_; + + Type type_; + +}; + +using RenderTicketPtr = std::shared_ptr; + +OLIVE_NAMESPACE_EXIT + +#endif // RENDERTICKET_H diff --git a/app/render/backend/renderticketwatcher.cpp b/app/render/backend/renderticketwatcher.cpp new file mode 100644 index 000000000..d5860311d --- /dev/null +++ b/app/render/backend/renderticketwatcher.cpp @@ -0,0 +1,82 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "renderticketwatcher.h" + +OLIVE_NAMESPACE_ENTER + +RenderTicketWatcher::RenderTicketWatcher(QObject *parent) : + QObject(parent), + ticket_(nullptr) +{ +} + +void RenderTicketWatcher::SetTicket(RenderTicketPtr ticket) +{ + // Ensure that a ticket has NOT already been set and that this ticket is NOT NULL + Q_ASSERT(!ticket_ && ticket); + + ticket_ = ticket; + + QMutexLocker locker(ticket->lock()); + + if (ticket_->IsFinished(false)) { + locker.unlock(); + emit Finished(); + } else { + connect(ticket_.get(), &RenderTicket::Finished, this, &RenderTicketWatcher::Finished); + } +} + +bool RenderTicketWatcher::WasCancelled() +{ + if (ticket_) { + return ticket_->WasCancelled(); + } else { + return false; + } +} + +bool RenderTicketWatcher::IsFinished() +{ + if (ticket_) { + return ticket_->IsFinished(); + } else { + return false; + } +} + +void RenderTicketWatcher::WaitForFinished() +{ + if (ticket_) { + ticket_->WaitForFinished(); + } +} + +QVariant RenderTicketWatcher::Get() +{ + if (ticket_) { + return ticket_->Get(); + } else { + return QVariant(); + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderticketwatcher.h b/app/render/backend/renderticketwatcher.h new file mode 100644 index 000000000..e25c488fc --- /dev/null +++ b/app/render/backend/renderticketwatcher.h @@ -0,0 +1,54 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef RENDERTICKETWATCHER_H +#define RENDERTICKETWATCHER_H + +#include "renderticket.h" + +OLIVE_NAMESPACE_ENTER + +class RenderTicketWatcher : public QObject +{ + Q_OBJECT +public: + RenderTicketWatcher(QObject* parent = nullptr); + + void SetTicket(RenderTicketPtr ticket); + + bool WasCancelled(); + + bool IsFinished(); + + void WaitForFinished(); + + QVariant Get(); + +signals: + void Finished(); + +private: + RenderTicketPtr ticket_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // RENDERTICKETWATCHER_H diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 0fdedb5f2..f6864d266 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -33,53 +33,14 @@ OLIVE_NAMESPACE_ENTER RenderWorker::RenderWorker(RenderBackend* parent) : parent_(parent), - viewer_(nullptr), available_(true), audio_mode_is_preview_(false) { } -RenderWorker::~RenderWorker() +void RenderWorker::RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, const rational &time) { - Close(); -} - -QByteArray RenderWorker::Hash(const rational &time, bool block_for_update) -{ - if (!viewer_) { - return QByteArray(); - } - - QMutexLocker locker(&lock_); - - UpdateData(block_for_update); - - QCryptographicHash hasher(QCryptographicHash::Sha1); - - // Embed video parameters into this hash - hasher.addData(reinterpret_cast(&video_params_.effective_width()), sizeof(int)); - hasher.addData(reinterpret_cast(&video_params_.effective_height()), sizeof(int)); - hasher.addData(reinterpret_cast(&video_params_.format()), sizeof(PixelFormat::Format)); - hasher.addData(reinterpret_cast(&video_params_.mode()), sizeof(RenderMode::Mode)); - - viewer_->Hash(hasher, time); - - emit FinishedJob(); - - return hasher.result(); -} - -FramePtr RenderWorker::RenderFrame(const rational &time, bool block_for_update) -{ - if (!viewer_) { - return nullptr; - } - - QMutexLocker locker(&lock_); - - UpdateData(block_for_update); - - NodeValueTable table = ProcessInput(viewer_->texture_input(), + NodeValueTable table = ProcessInput(viewer->texture_input(), TimeRange(time, time + video_params_.time_base())); QVariant texture = table.Get(NodeParam::kTexture); @@ -97,44 +58,20 @@ FramePtr RenderWorker::RenderFrame(const rational &time, bool block_for_update) TextureToFrame(texture, frame, video_download_matrix_); } - emit FinishedJob(); + ticket->Finish(QVariant::fromValue(frame)); - return frame; + FinishedJob(); } -SampleBufferPtr RenderWorker::RenderAudio(const TimeRange &range, bool block_for_update) +void RenderWorker::RenderAudio(RenderTicketPtr ticket, ViewerOutput* viewer, const TimeRange &range) { - if (!viewer_) { - return nullptr; - } - - QMutexLocker locker(&lock_); - - parent_->WorkerStartedRenderingAudio(range); - - UpdateData(block_for_update); - - audio_render_time_ = range; - - NodeValueTable table = ProcessInput(viewer_->samples_input(), range); + NodeValueTable table = ProcessInput(viewer->samples_input(), range); QVariant samples = table.Get(NodeParam::kSamples); - return samples.value(); -} + ticket->Finish(samples); -void RenderWorker::UpdateData(bool block_for_update) -{ - // FIXME: This is pretty trashy. It works, but it's not good. Should probably be changed at some - // point. - if (block_for_update) { - QMetaObject::invokeMethod(parent_, - "UpdateInstance", - Qt::BlockingQueuedConnection, - OLIVE_NS_ARG(RenderWorker*, this)); - } else { - parent_->UpdateInstance(this); - } + FinishedJob(); } NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const TimeRange &range) @@ -188,7 +125,7 @@ NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const { // Save waveform to file - Block* src_block = static_cast(copy_map_.key(b)); + Block* src_block = static_cast(copy_map_->key(b)); QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString()); QDir waveform_loc = local_appdata_dir.filePath(QStringLiteral("waveform")); waveform_loc.mkpath("."); @@ -444,143 +381,4 @@ DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream) return decoder; } -void RenderWorker::Queue(NodeInput *input) -{ - if (!queued_updates_.isEmpty()) { - // First, check if anything in our queue is a dependency of this input. If so, we should remove - // it and just update this input. - - // First we need to find our copy of the input being queued - Node* our_copy_node = copy_map_.value(input->parentNode()); - - if (our_copy_node) { - NodeInput* our_copy = our_copy_node->GetInputWithID(input->id()); - QList our_copy_deps = our_copy->GetDependencies(our_copy); - - for (int i=0;iparentNode()); - - // If this input isn't connected anymore, it obviously won't come up as a dependency - if (our_copy_deps.contains(check_input_our_copy)) { - queued_updates_.removeAt(i); - i--; - } - } - } - } - - queued_updates_.append(input); -} - -void RenderWorker::ProcessQueue() -{ - while (!queued_updates_.isEmpty()) { - CopyNodeInputValue(queued_updates_.takeFirst()); - } -} - -void RenderWorker::CopyNodeInputValue(NodeInput *input) -{ - // Find our copy of this parameter - Node* our_copy_node = copy_map_.value(input->parentNode()); - NodeInput* our_copy = our_copy_node->GetInputWithID(input->id()); - - // Copy the standard/keyframe values between these two inputs - NodeInput::CopyValues(input, - our_copy, - false); - - // Handle connections - if (input->IsConnected() || our_copy->IsConnected()) { - // If one of the inputs is connected, it's likely this change came from connecting or - // disconnecting whatever was connected to it - - // We start by removing all old dependencies from the map - QList old_deps = our_copy->GetExclusiveDependencies(); - foreach (Node* i, old_deps) { - copy_map_.take(copy_map_.key(i))->deleteLater(); - } - - // And clear any other edges - while (!our_copy->edges().isEmpty()) { - NodeParam::DisconnectEdge(our_copy->edges().first()); - } - - // Then we copy all node dependencies and connections (if there are any) - CopyNodeMakeConnection(input, our_copy); - } - - // Call on sub-elements too - if (input->IsArray()) { - foreach (NodeInput* i, static_cast(input)->sub_params()) { - CopyNodeInputValue(i); - } - } -} - -Node* RenderWorker::CopyNodeConnections(Node* src_node) -{ - // Check if this node is already in the map - Node* dst_node = copy_map_.value(src_node); - - // If not, create it now - if (!dst_node) { - dst_node = src_node->copy(); - - if (dst_node->IsTrack()) { - // Hack that ensures the track type is set since we don't bother copying the whole timeline - static_cast(dst_node)->set_track_type(static_cast(src_node)->track_type()); - } - - copy_map_.insert(src_node, dst_node); - } - - // Make sure its values are copied - Node::CopyInputs(src_node, dst_node, false); - - // Copy all connections - QList src_node_inputs = src_node->GetInputsIncludingArrays(); - QList dst_node_inputs = dst_node->GetInputsIncludingArrays(); - - for (int i=0;iIsConnected()) { - Node* dst_node = CopyNodeConnections(src_input->get_connected_node()); - - NodeOutput* corresponding_output = dst_node->GetOutputWithID(src_input->get_connected_output()->id()); - - NodeParam::ConnectEdge(corresponding_output, - dst_input); - } -} - -void RenderWorker::Init(ViewerOutput* viewer) -{ - viewer_ = static_cast(viewer->copy()); - copy_map_.insert(viewer, viewer_); - - Queue(viewer->texture_input()); - Queue(viewer->samples_input()); - ProcessQueue(); -} - -void RenderWorker::Close() -{ - // Delete all the nodes - qDeleteAll(copy_map_); - copy_map_.clear(); - - viewer_ = nullptr; -} - OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index ebfe74ff1..fc8cf5fc2 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -26,6 +26,7 @@ #include "decodercache.h" #include "node/traverser.h" #include "node/output/viewer/viewer.h" +#include "renderticket.h" OLIVE_NAMESPACE_ENTER @@ -37,14 +38,6 @@ class RenderWorker : public QObject, public NodeTraverser public: RenderWorker(RenderBackend* parent); - virtual ~RenderWorker() override; - - void Init(ViewerOutput *viewer); - void Close(); - - void Queue(NodeInput* input); - void ProcessQueue(); - bool IsAvailable() const { return available_; @@ -55,11 +48,6 @@ public: available_ = a; } - ViewerOutput* GetViewer() const - { - return viewer_; - } - void SetVideoParams(const VideoRenderingParams& params) { video_params_ = params; @@ -80,6 +68,11 @@ public: audio_mode_is_preview_ = audio_mode_is_preview; } + void SetCopyMap(QHash* copy_map) + { + copy_map_ = copy_map; + } + /** * @brief Return a unique ID for the image generated at this time * @@ -90,7 +83,7 @@ public: * * SHA-1 hash or empty QByteArray if no viewer node is set. */ - QByteArray Hash(const rational &time, bool block_for_update); + void Hash(RenderTicketPtr ticket, ViewerOutput *viewer, const QList& times); /** * @brief Render the frame at this time @@ -103,9 +96,9 @@ public: * function will still return a blank frame with the same parameters. If no viewer node is set, * nullptr is returned. */ - FramePtr RenderFrame(const rational& time, bool block_for_update); + void RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, const rational &time); - SampleBufferPtr RenderAudio(const TimeRange& range, bool block_for_update); + void RenderAudio(RenderTicketPtr ticket, ViewerOutput* viewer, const TimeRange& range); protected: virtual void TextureToFrame(const QVariant& texture, FramePtr frame, const QMatrix4x4 &mat) const = 0; @@ -135,16 +128,10 @@ signals: void FinishedJob(); private: - void UpdateData(bool block_for_update); - NodeValue GetDataFromStream(StreamPtr stream, const TimeRange& input_time); DecoderPtr ResolveDecoderFromInput(StreamPtr stream); - void CopyNodeInputValue(NodeInput* input); - Node *CopyNodeConnections(Node *src_node); - void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input); - RenderBackend* parent_; VideoRenderingParams video_params_; @@ -165,15 +152,12 @@ private: DecoderCache decoder_cache_; - ViewerOutput* viewer_; TimeRange audio_render_time_; - QList queued_updates_; - QHash copy_map_; bool available_; bool audio_mode_is_preview_; - QMutex lock_; + QHash* copy_map_; }; diff --git a/app/task/cache/cache.cpp b/app/task/cache/cache.cpp index 0a4b99df2..348b21046 100644 --- a/app/task/cache/cache.cpp +++ b/app/task/cache/cache.cpp @@ -48,7 +48,7 @@ bool CacheTask::Run() } } - Render(range_to_cache, QMatrix4x4(), false); + Render(range_to_cache, QMatrix4x4(), false, true); download_threads_.waitForDone(); diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 232d2baa9..587af6ab1 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -85,7 +85,7 @@ bool ExportTask::Run() } // Start render process - Render({range}, mat, params_.audio_enabled()); + Render({range}, mat, params_.audio_enabled(), false); bool success = true; diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 9f4915ca9..ff799f104 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -34,7 +34,7 @@ RenderTask::RenderTask(ViewerOutput* viewer, const VideoRenderingParams &vparams struct TimeHashFuturePair { rational time; - QFuture hash_future; + RenderTicketPtr hash_future; }; struct HashTimePair { @@ -44,12 +44,12 @@ struct HashTimePair { struct HashFrameFuturePair { QByteArray hash; - QFuture frame_future; + RenderTicketPtr frame_future; }; struct RangeSampleFuturePair { TimeRange range; - QFuture sample_future; + RenderTicketPtr sample_future; }; struct HashDownloadFuturePair { @@ -59,31 +59,42 @@ struct HashDownloadFuturePair { void RenderTask::Render(const TimeRangeList& range_to_cache, const QMatrix4x4& mat, - bool audio_enabled) + bool audio_enabled, + bool use_disk_cache) { OpenGLBackend backend; - backend.SetAutomaticAudio(false); + backend.moveToThread(qApp->thread()); + backend.SetViewerNode(viewer_); backend.SetVideoParams(video_params_); backend.SetAudioParams(audio_params_); backend.SetVideoDownloadMatrix(mat); - // Get hashes for each frame - QLinkedList hash_list; + // Get hashes for each frame and group likes together + QMap< QByteArray, QLinkedList > times_to_render; { QList times = viewer_->video_frame_cache()->GetFrameListFromTimeRange(range_to_cache); - foreach (const rational& r, times) { - hash_list.append({r, backend.Hash(r, false)}); - } - } + QFuture > hash_future = backend.Hash(times); + QList hashes = hash_future.result(); - // Determine any duplicates - QMap< QByteArray, QLinkedList > times_to_render; - foreach (const TimeHashFuturePair& i, hash_list) { - times_to_render[i.hash_future.result()].append(i.time); + // Determine any duplicates + int index = 0; + foreach (const QByteArray& hash, hashes) { + const rational& time = times.at(index); + + if (use_disk_cache + && QFileInfo::exists(viewer_->video_frame_cache()->CachePathName(hash, video_params_.format()))) { + // Already exists, no need to render it again + FrameDownloaded(hash, {time}); + } else { + times_to_render[hash].append(time); + } + + index++; + } } // Render all frames necessary @@ -96,35 +107,25 @@ void RenderTask::Render(const TimeRangeList& range_to_cache, QMap< QByteArray, QLinkedList >::const_iterator i; for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) { const QByteArray& hash = i.key(); + const rational& time = i.value().first(); - if (QFileInfo::exists(viewer_->video_frame_cache()->CachePathName(hash, video_params_.format()))) { + bool inserted = false; - // Already exists, no need to render it again - FrameDownloaded(hash, i.value()); - - } else { - - const rational& time = i.value().first(); - - bool inserted = false; - - for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) { - if (sorted_iterator->time > time) { - sorted_times.insert(sorted_iterator, {time, hash}); - inserted = true; - break; - } - } - - if (!inserted) { - sorted_times.append({time, hash}); + for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) { + if (sorted_iterator->time > time) { + sorted_times.insert(sorted_iterator, {time, hash}); + inserted = true; + break; } + } + if (!inserted) { + sorted_times.append({time, hash}); } } foreach (const HashTimePair& p, sorted_times) { - render_lookup_table.append({p.hash, backend.RenderFrame(p.time, false, false)}); + render_lookup_table.append({p.hash, backend.RenderFrame(p.time)}); } } @@ -134,7 +135,7 @@ void RenderTask::Render(const TimeRangeList& range_to_cache, QList ranges = RenderBackend::SplitRangeIntoChunks(r); foreach (const TimeRange& split, ranges) { - audio_lookup_table.append({split, backend.RenderAudio(split, false)}); + audio_lookup_table.append({split, backend.RenderAudio(split)}); } } } @@ -159,8 +160,8 @@ void RenderTask::Render(const TimeRangeList& range_to_cache, i = render_lookup_table.begin(); while (i != render_lookup_table.end()) { - if (i->frame_future.isFinished()) { - FramePtr f = i->frame_future.result(); + if (i->frame_future->IsFinished()) { + FramePtr f = i->frame_future->Get().value(); // Start multithreaded download here download_futures.append({i->hash, DownloadFrame(f, i->hash)}); @@ -192,8 +193,8 @@ void RenderTask::Render(const TimeRangeList& range_to_cache, k = audio_lookup_table.begin(); while (k != audio_lookup_table.end()) { - if (k->sample_future.isFinished()) { - AudioDownloaded(k->range, k->sample_future.result()); + if (k->sample_future->IsFinished()) { + AudioDownloaded(k->range, k->sample_future->Get().value()); k = audio_lookup_table.erase(k); } else { diff --git a/app/task/render/render.h b/app/task/render/render.h index dfd8e0bc4..d7407c97e 100644 --- a/app/task/render/render.h +++ b/app/task/render/render.h @@ -34,7 +34,7 @@ public: RenderTask(ViewerOutput* viewer, const VideoRenderingParams &vparams, const AudioRenderingParams &aparams); protected: - void Render(const TimeRangeList &range_to_cache, const QMatrix4x4 &mat, bool audio_enabled); + void Render(const TimeRangeList &range_to_cache, const QMatrix4x4 &mat, bool audio_enabled, bool use_disk_cache); virtual QFuture DownloadFrame(FramePtr frame, const QByteArray &hash) = 0; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 4a8b900f0..d06dd828e 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -111,6 +111,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : // Start background renderer renderer_ = new OpenGLBackend(this); + renderer_->SetUpdateWithGraph(true); // Setup cache wait timer (waits a few seconds of inactivity before caching) cache_wait_timer_.setInterval(100); @@ -424,6 +425,11 @@ FramePtr DecodeCachedImage(const QString &fn, const rational& time) return frame; } +void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) +{ + ticket->Finish(QVariant::fromValue(DecodeCachedImage(fn, time))); +} + void ViewerWidget::UpdateTextureFromNode(const rational& time) { if (!FrameExistsAtTime(time)) { @@ -459,16 +465,10 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time) } // Frame was not in queue, will require rendering or decoding from cache - QFutureWatcher* watcher = new QFutureWatcher(); - - connect(watcher, - &QFutureWatcher::finished, - this, - &ViewerWidget::RendererGeneratedFrame); - + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrame); nonqueue_watchers_.append(watcher); - - watcher->setFuture(GetFrame(time, true, true)); + watcher->SetTicket(GetFrame(time, true)); } void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) @@ -636,12 +636,9 @@ void ViewerWidget::RequestNextFrameForQueue() playback_queue_next_frame_ += playback_speed_; - QFutureWatcher* watcher = new QFutureWatcher(); - connect(watcher, - &QFutureWatcher::finished, - this, - &ViewerWidget::RendererGeneratedFrameForQueue); - watcher->setFuture(GetFrame(next_time, false, true)); + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue); + watcher->SetTicket(GetFrame(next_time, false)); } PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const @@ -649,18 +646,24 @@ PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const return PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline); } -QFuture ViewerWidget::GetFrame(const rational &t, bool clear_render_queue, bool block_update) +RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool clear_render_queue) { QByteArray cached_hash = GetConnectedNode()->video_frame_cache()->GetHash(t); if (cached_hash.isEmpty()) { // Frame hasn't been cached, start render job - return renderer_->RenderFrame(t, clear_render_queue, block_update); + if (clear_render_queue) { + renderer_->ClearVideoQueue(); + } + + return renderer_->RenderFrame(t); } else { // Frame has been cached, grab the frame QString cache_fn = GetConnectedNode()->video_frame_cache()->CachePathName(cached_hash, GetCurrentPixelFormat()); - return QtConcurrent::run(DecodeCachedImage, cache_fn, t); + RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeVideo, TimeRange(t, t)); + QtConcurrent::run(DecodeCachedImage, ticket, cache_fn, t); + return ticket; } } @@ -785,57 +788,45 @@ void ViewerWidget::ContextMenuScopeTriggered(QAction *action) void ViewerWidget::RendererGeneratedFrame() { - QFutureWatcher* watcher = static_cast*>(sender()); - FramePtr frame = watcher->result(); + RenderTicketWatcher* ticket = static_cast(sender()); - if (nonqueue_watchers_.contains(watcher)) { - while (!nonqueue_watchers_.isEmpty()) { - if (nonqueue_watchers_.takeFirst() == watcher) { - break; + if (!ticket->WasCancelled()) { + FramePtr frame = ticket->Get().value(); + + if (nonqueue_watchers_.contains(ticket)) { + while (!nonqueue_watchers_.isEmpty()) { + if (nonqueue_watchers_.takeFirst() == ticket) { + break; + } } - } - SetDisplayImage(frame, false); + SetDisplayImage(frame, false); + } } - watcher->deleteLater(); + ticket->deleteLater(); } void ViewerWidget::RendererGeneratedFrameForQueue() { - QFutureWatcher* watcher = static_cast*>(sender()); - FramePtr frame = watcher->result(); - watcher->deleteLater(); + RenderTicketWatcher* watcher = static_cast(sender()); - // Ignore this signal if we've paused now - if (IsPlaying() || prequeuing_) { - playback_queue_.AppendTimewise({frame->timestamp(), frame}, playback_speed_); + if (!watcher->WasCancelled()) { + FramePtr frame = watcher->Get().value(); - foreach (ViewerWindow* window, windows_) { - window->queue()->AppendTimewise({frame->timestamp(), frame}, playback_speed_); + // Ignore this signal if we've paused now + if (IsPlaying() || prequeuing_) { + playback_queue_.AppendTimewise({frame->timestamp(), frame}, playback_speed_); + + foreach (ViewerWindow* window, windows_) { + window->queue()->AppendTimewise({frame->timestamp(), frame}, playback_speed_); + } + + if (prequeuing_ && playback_queue_.size() == kMaxPreQueueSize) { + prequeuing_ = false; + FinishPlayPreprocess(); + } } - - if (prequeuing_ && playback_queue_.size() == kMaxPreQueueSize) { - prequeuing_ = false; - FinishPlayPreprocess(); - } - } -} - -void ViewerWidget::HashGenerated() -{ - QFutureWatcher* watcher = static_cast*>(sender()); - - if (hash_watchers_.contains(watcher)) { - FrameHashCache* cache = GetConnectedNode()->video_frame_cache(); - - QString cache_fn = cache->CachePathName(watcher->result(), GetCurrentPixelFormat()); - - if (QFileInfo::exists(cache_fn)) { - cache->SetHash(hash_watchers_.value(watcher), watcher->result()); - } - - hash_watchers_.remove(watcher); } watcher->deleteLater(); @@ -1211,16 +1202,6 @@ void ViewerWidget::ViewerInvalidatedRange(const TimeRange &range) if (!(qApp->mouseButtons() & Qt::LeftButton)) { cache_wait_timer_.start(); } - - /* - QList invalidated_frames = GetConnectedNode()->video_frame_cache()->GetFrameListFromTimeRange({range}); - foreach (const rational& r, invalidated_frames) { - QFutureWatcher* watcher = new QFutureWatcher(); - connect(watcher, &QFutureWatcher::finished, this, &ViewerWidget::HashGenerated); - hash_watchers_.insert(watcher, r); - watcher->setFuture(renderer_->Hash(r, true)); - } - */ } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 491dfc543..680e6810a 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -33,6 +33,7 @@ #include "node/output/viewer/viewer.h" #include "panel/scope/scope.h" #include "render/backend/opengl/openglbackend.h" +#include "render/backend/renderticketwatcher.h" #include "task/cache/cache.h" #include "viewerdisplay.h" #include "viewerplaybacktimer.h" @@ -194,7 +195,7 @@ private: PixelFormat::Format GetCurrentPixelFormat() const; - QFuture GetFrame(const rational& t, bool clear_render_queue, bool block_update); + RenderTicketPtr GetFrame(const rational& t, bool clear_render_queue); void FinishPlayPreprocess(); @@ -240,9 +241,7 @@ private: bool prequeuing_; - QList< QFutureWatcher* > nonqueue_watchers_; - - QHash*, rational> hash_watchers_; + QList nonqueue_watchers_; QTimer cache_wait_timer_; @@ -289,8 +288,6 @@ private slots: void RendererGeneratedFrameForQueue(); - void HashGenerated(); - void StartBackgroundCaching(); void BackgroundCacheFinished(Task *t);