From 863ae1b913149e28c660a92dbd77c87a310632bd Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 14 May 2020 14:43:03 +1000 Subject: [PATCH] a first attempt at a new render system --- app/codec/ffmpeg/ffmpegdecoder.cpp | 6 +- app/core.cpp | 3 +- app/dialog/export/export.cpp | 7 +- app/node/CMakeLists.txt | 2 - app/node/dependency.cpp | 64 -- app/node/output/track/track.cpp | 2 +- app/node/output/track/track.h | 2 +- app/node/output/viewer/viewer.cpp | 63 +- app/node/output/viewer/viewer.h | 62 +- app/node/traverser.cpp | 52 +- app/node/traverser.h | 19 +- app/panel/viewer/viewerbase.cpp | 5 - app/panel/viewer/viewerbase.h | 2 - app/project/item/sequence/sequence.cpp | 2 +- app/render/CMakeLists.txt | 6 + app/render/audioplaybackcache.cpp | 56 ++ .../audioplaybackcache.h} | 44 +- app/render/backend/CMakeLists.txt | 15 - app/render/backend/audio/CMakeLists.txt | 24 - app/render/backend/audio/audiobackend.cpp | 116 --- app/render/backend/audio/audiobackend.h | 52 -- app/render/backend/audio/audioworker.cpp | 105 --- app/render/backend/audiorenderbackend.cpp | 275 ------- app/render/backend/audiorenderbackend.h | 105 --- app/render/backend/audiorenderworker.cpp | 173 ----- app/render/backend/audiorenderworker.h | 59 -- app/render/backend/exporter.cpp | 69 +- app/render/backend/exporter.h | 6 +- app/render/backend/opengl/CMakeLists.txt | 2 - app/render/backend/opengl/openglbackend.cpp | 69 +- app/render/backend/opengl/openglbackend.h | 21 +- .../backend/opengl/openglcolorprocessor.h | 3 + app/render/backend/opengl/openglproxy.h | 6 +- app/render/backend/opengl/openglworker.cpp | 84 --- app/render/backend/opengl/openglworker.h | 56 -- app/render/backend/renderbackend.cpp | 673 +++++++++--------- app/render/backend/renderbackend.h | 189 +++-- app/render/backend/renderworker.cpp | 128 ---- app/render/backend/renderworker.h | 81 --- app/render/backend/videorenderbackend.cpp | 354 --------- app/render/backend/videorenderbackend.h | 150 ---- app/render/backend/videorenderworker.cpp | 251 ------- app/render/backend/videorenderworker.h | 140 ---- ...enderframecache.cpp => framehashcache.cpp} | 116 ++- ...deorenderframecache.h => framehashcache.h} | 51 +- app/render/playbackcache.cpp | 84 +++ .../audio/audioworker.h => playbackcache.h} | 44 +- app/widget/timebased/timebased.cpp | 10 +- app/widget/timelinewidget/timelinewidget.cpp | 14 +- app/widget/timelinewidget/tool/ripple.cpp | 2 +- app/widget/timelinewidget/undo/undo.cpp | 2 +- app/widget/viewer/audiowaveformview.cpp | 34 +- app/widget/viewer/audiowaveformview.h | 6 +- app/widget/viewer/gizmotraverser.cpp | 2 +- app/widget/viewer/gizmotraverser.h | 2 +- app/widget/viewer/viewer.cpp | 100 ++- app/widget/viewer/viewer.h | 13 +- app/widget/viewer/viewerdisplay.cpp | 2 +- app/widget/viewer/viewerdisplay.h | 4 +- app/window/mainwindow/mainwindow.cpp | 2 - 60 files changed, 927 insertions(+), 3164 deletions(-) delete mode 100644 app/node/dependency.cpp create mode 100644 app/render/audioplaybackcache.cpp rename app/{node/dependency.h => render/audioplaybackcache.h} (59%) delete mode 100644 app/render/backend/audio/CMakeLists.txt delete mode 100644 app/render/backend/audio/audiobackend.cpp delete mode 100644 app/render/backend/audio/audiobackend.h delete mode 100644 app/render/backend/audio/audioworker.cpp delete mode 100644 app/render/backend/audiorenderbackend.cpp delete mode 100644 app/render/backend/audiorenderbackend.h delete mode 100644 app/render/backend/audiorenderworker.cpp delete mode 100644 app/render/backend/audiorenderworker.h delete mode 100644 app/render/backend/opengl/openglworker.cpp delete mode 100644 app/render/backend/opengl/openglworker.h delete mode 100644 app/render/backend/renderworker.cpp delete mode 100644 app/render/backend/renderworker.h delete mode 100644 app/render/backend/videorenderbackend.cpp delete mode 100644 app/render/backend/videorenderbackend.h delete mode 100644 app/render/backend/videorenderworker.cpp delete mode 100644 app/render/backend/videorenderworker.h rename app/render/{backend/videorenderframecache.cpp => framehashcache.cpp} (73%) rename app/render/{backend/videorenderframecache.h => framehashcache.h} (61%) create mode 100644 app/render/playbackcache.cpp rename app/render/{backend/audio/audioworker.h => playbackcache.h} (50%) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 5b2c45a90..737ca8770 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -42,7 +42,7 @@ extern "C" { #include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "ffmpegcommon.h" -#include "render/backend/videorenderframecache.h" +#include "render/framehashcache.h" #include "render/diskmanager.h" #include "render/pixelformat.h" @@ -662,7 +662,7 @@ void SaveCacheFrame(FFmpegDecoder* decoder, &converted_linesize); scaler_lock.unlock(); - if (!VideoRenderFrameCache::SaveCacheFrame(dst_fn, converted_buffer.data(), params)) { + if (!FrameHashCache::SaveCacheFrame(dst_fn, converted_buffer.data(), params)) { qCritical() <<" Failed to save cache frame" << dst_fn; } @@ -1286,7 +1286,7 @@ QString FFmpegDecoder::GetProxyFrameFilename(const int64_t ×tamp, const int { QString dst_fn = GetProxyFilename(divider); dst_fn.append(QString::number(timestamp)); - dst_fn.append(VideoRenderFrameCache::GetFormatExtension(native_pix_fmt_)); + dst_fn.append(FrameHashCache::GetFormatExtension(native_pix_fmt_)); return dst_fn; } diff --git a/app/core.cpp b/app/core.cpp index 25123d675..1a7aa0dc1 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -379,7 +379,7 @@ void Core::DialogExportShow() TimeBasedPanel* latest_time_based = PanelManager::instance()->MostRecentlyFocused(); if (latest_time_based && latest_time_based->GetConnectedViewer()) { - if (latest_time_based->GetConnectedViewer()->Length() == 0) { + if (latest_time_based->GetConnectedViewer()->GetLength() == 0) { QMessageBox::critical(main_window_, tr("Error"), tr("This Sequence is empty. There is nothing to export."), @@ -525,7 +525,6 @@ void Core::ProjectWasModified(bool e) void Core::DeclareTypesForQt() { - qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 3071d92c5..7131290d4 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -228,11 +228,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : preview_viewer_->SetColorMenuEnabled(false); preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); - // Update renderer - // FIXME: This is going to be VERY slow since it will need to hash every single frame. It would be better to have a - // the renderer save the map as some sort of file that this can load. - preview_viewer_->video_renderer()->InvalidateCache(TimeRange(0, viewer_node_->Length()), nullptr); - progress_timer_.setInterval(1000); connect(&progress_timer_, &QTimer::timeout, this, &ExportDialog::UpdateTimeLabels); } @@ -585,7 +580,7 @@ ExportParams ExportDialog::GenerateParams() const ExportParams params; params.SetFilename(filename_edit_->text()); - params.SetExportLength(viewer_node_->Length()); + params.SetExportLength(viewer_node_->GetLength()); if (video_tab_->scaling_method_combobox()->isEnabled()) { params.set_video_scaling_method(static_cast(video_tab_->scaling_method_combobox()->currentData().toInt())); diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index 6f31b252c..744bc7da9 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -24,8 +24,6 @@ add_subdirectory(output) set(OLIVE_SOURCES ${OLIVE_SOURCES} - node/dependency.h - node/dependency.cpp node/edge.h node/edge.cpp node/factory.h diff --git a/app/node/dependency.cpp b/app/node/dependency.cpp deleted file mode 100644 index 398d6c7e0..000000000 --- a/app/node/dependency.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/*** - - 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 "dependency.h" - -#include "node.h" - -OLIVE_NAMESPACE_ENTER - -NodeDependency::NodeDependency() : - node_(nullptr) -{ -} - -NodeDependency::NodeDependency(const Node *node, const TimeRange &range) : - node_(node), - range_(range) -{ -} - -NodeDependency::NodeDependency(const Node *node, const rational &in, const rational &out) : - node_(node), - range_(in, out) -{ -} - -const Node *NodeDependency::node() const -{ - return node_; -} - -const rational& NodeDependency::in() const -{ - return range_.in(); -} - -const rational &NodeDependency::out() const -{ - return range_.out(); -} - -const TimeRange &NodeDependency::range() const -{ - return range_; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 240dc514e..4fdc9835f 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -55,7 +55,7 @@ void TrackOutput::set_track_type(const Timeline::TrackType &track_type) track_type_ = track_type; } -const Timeline::TrackType& TrackOutput::track_type() +const Timeline::TrackType& TrackOutput::track_type() const { return track_type_; } diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 781c4eb96..cd0c7bd6e 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -35,7 +35,7 @@ class TrackOutput : public Block public: TrackOutput(); - const Timeline::TrackType& track_type(); + const Timeline::TrackType& track_type() const; void set_track_type(const Timeline::TrackType& track_type); virtual Type type() const override; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 445598cb0..289298ab8 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -55,6 +55,8 @@ ViewerOutput::ViewerOutput() // Create UUID for this node uuid_ = QUuid::createUuid(); + + connect(this, &ViewerOutput::LengthChanged, &video_frame_cache_, &FrameHashCache::SetLength); } Node *ViewerOutput::copy() const @@ -82,22 +84,16 @@ QString ViewerOutput::Description() const return tr("Interface between a Viewer panel and the node system."); } -NodeInput *ViewerOutput::texture_input() const -{ - return texture_input_; -} - -NodeInput *ViewerOutput::samples_input() const -{ - return samples_input_; -} - void ViewerOutput::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source) { if (from == texture_input()) { - emit VideoChangedBetween(range, source); + emit GraphChangedFrom(from, source); + + video_frame_cache_.Invalidate(range); } else if (from == samples_input()) { - emit AudioChangedBetween(range, source); + emit GraphChangedFrom(from, source); + + audio_playback_cache_.Invalidate(range); } Node::InvalidateCache(range, from, source); @@ -112,16 +108,6 @@ void ViewerOutput::InvalidateVisible(NodeInput* from, NodeInput *source) Node::InvalidateVisible(from, source); } -const VideoParams &ViewerOutput::video_params() const -{ - return video_params_; -} - -const AudioParams &ViewerOutput::audio_params() const -{ - return audio_params_; -} - void ViewerOutput::set_video_params(const VideoParams &video) { video_params_ = video; @@ -134,34 +120,31 @@ void ViewerOutput::set_video_params(const VideoParams &video) void ViewerOutput::set_audio_params(const AudioParams &audio) { audio_params_ = audio; + + emit AudioParamsChanged(); } -rational ViewerOutput::Length() +rational ViewerOutput::GetLength() { NodeTraverser traverser; rational video_length; if (texture_input_->IsConnected()) { - NodeValueTable t = traverser.ProcessNode(NodeDependency(texture_input_->get_connected_node(), 0, 0)); + NodeValueTable t = traverser.GenerateTable(texture_input_->get_connected_node(), 0, 0); video_length = t.Get(NodeParam::kNumber, "length").value(); } rational audio_length; if (samples_input_->IsConnected()) { - NodeValueTable t = traverser.ProcessNode(NodeDependency(samples_input_->get_connected_node(), 0, 0)); + NodeValueTable t = traverser.GenerateTable(samples_input_->get_connected_node(), 0, 0); audio_length = t.Get(NodeParam::kNumber, "length").value(); } return qMax(video_length, qMax(audio_length, timeline_length_)); } -const QUuid &ViewerOutput::uuid() const -{ - return uuid_; -} - void ViewerOutput::UpdateTrackCache() { track_cache_.clear(); @@ -231,11 +214,6 @@ void ViewerOutput::Retranslate() } } -const QString &ViewerOutput::media_name() const -{ - return media_name_; -} - void ViewerOutput::set_media_name(const QString &name) { media_name_ = name; @@ -243,21 +221,6 @@ void ViewerOutput::set_media_name(const QString &name) emit MediaNameChanged(media_name_); } -const QVector& ViewerOutput::Tracks() const -{ - return track_cache_; -} - -NodeInput *ViewerOutput::track_input(Timeline::TrackType type) const -{ - return track_inputs_.at(type); -} - -TrackList *ViewerOutput::track_list(Timeline::TrackType type) const -{ - return track_lists_.at(type); -} - void ViewerOutput::TrackListAddedBlock(Block *block, int index) { Timeline::TrackType type = static_cast(sender())->type(); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 3a5b3fa78..2c51e0181 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -28,8 +28,10 @@ #include "node/output/track/track.h" #include "node/output/track/tracklist.h" #include "node/node.h" -#include "render/videoparams.h" #include "render/audioparams.h" +#include "render/audioplaybackcache.h" +#include "render/framehashcache.h" +#include "render/videoparams.h" #include "timeline/trackreference.h" OLIVE_NAMESPACE_ENTER @@ -52,39 +54,66 @@ public: virtual QList Category() const override; virtual QString Description() const override; - NodeInput* texture_input() const; - NodeInput* samples_input() const; + NodeInput* texture_input() const { + return texture_input_; + } + + NodeInput* samples_input() const { + return samples_input_; + } virtual void InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput* source) override; virtual void InvalidateVisible(NodeInput *from, NodeInput* source) override; - const VideoParams& video_params() const; - const AudioParams& audio_params() const; + const VideoParams& video_params() const { + return video_params_; + } + + const AudioParams& audio_params() const { + return audio_params_; + } void set_video_params(const VideoParams& video); void set_audio_params(const AudioParams& audio); - rational Length(); + rational GetLength(); - const QUuid& uuid() const; + const QUuid& uuid() const { + return uuid_; + } - const QVector &Tracks() const; + const QVector &GetTracks() const { + return track_cache_; + } - NodeInput* track_input(Timeline::TrackType type) const; + NodeInput* track_input(Timeline::TrackType type) const { + return track_inputs_.at(type); + } - TrackList* track_list(Timeline::TrackType type) const; + TrackList* track_list(Timeline::TrackType type) const { + return track_lists_.at(type); + } virtual void Retranslate() override; - const QString& media_name() const; + const QString& media_name() const { + return media_name_; + } + void set_media_name(const QString& name); + FrameHashCache* video_frame_cache() { + return &video_frame_cache_; + } + + AudioPlaybackCache* audio_playback_cache() { + return &audio_playback_cache_; + } + signals: void TimebaseChanged(const rational&); - void VideoChangedBetween(const TimeRange& range, NodeInput* source); - - void AudioChangedBetween(const TimeRange& range, NodeInput* source); + void GraphChangedFrom(NodeInput* from, NodeInput* source); void VisibleInvalidated(NodeInput* source); @@ -93,6 +122,7 @@ signals: void SizeChanged(int width, int height); void VideoParamsChanged(); + void AudioParamsChanged(); void BlockAdded(Block* block, TrackReference track); void BlockRemoved(Block* block); @@ -125,6 +155,10 @@ private: QString media_name_; + FrameHashCache video_frame_cache_; + + AudioPlaybackCache audio_playback_cache_; + private slots: void UpdateTrackCache(); diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 9f5a1f8f5..d99647a2b 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -24,7 +24,7 @@ OLIVE_NAMESPACE_ENTER -NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRange &range) +NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRange &range) const { NodeValueDatabase database; @@ -38,7 +38,17 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa TimeRange input_time = node->InputTimeAdjustment(input, range); - NodeValueTable table = ProcessInput(input, input_time); + NodeValueTable table; + + if (input->IsConnected()) { + // Value will equal something from the connected node, follow it + table = GenerateTable(input->get_connected_node(), range); + } else { + // Push onto the table the value at this time from the input + QVariant input_value = input->get_value_at_time(range.in()); + + table.Push(input->data_type(), input_value); + } // Exception for Footage types where we actually retrieve some Footage data from a decoder if (input->data_type() == NodeParam::kFootage) { @@ -63,29 +73,32 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa return database; } -NodeValueTable NodeTraverser::ProcessNode(const NodeDependency& dep) +NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& range) const { - const Node* node = dep.node(); - - if (node->IsTrack()) { + if (n->IsTrack()) { // If the range is not wholly contained in this Block, we'll need to do some extra processing - return RenderBlock(static_cast(node), dep.range()); + return GenerateBlockTable(static_cast(n), range); } // FIXME: Cache certain values here if we've already processed them before // Generate database of input values of node - NodeValueDatabase database = GenerateDatabase(node, dep.range()); + NodeValueDatabase database = GenerateDatabase(n, range); // By this point, the node should have all the inputs it needs to render correctly - NodeValueTable table = node->Value(database); + NodeValueTable table = n->Value(database); - ProcessNodeEvent(node, dep.range(), database, table); + ProcessNodeEvent(n, range, database, table); return table; } -NodeValueTable NodeTraverser::RenderBlock(const TrackOutput *track, const TimeRange &range) +NodeValueTable NodeTraverser::GenerateTable(const Node *n, const rational &in, const rational &out) const +{ + return GenerateTable(n, TimeRange(in, out)); +} + +NodeValueTable NodeTraverser::GenerateBlockTable(const TrackOutput *track, const TimeRange &range) const { // By default, just follow the in point Block* active_block = track->BlockAtTime(range.in()); @@ -93,7 +106,7 @@ NodeValueTable NodeTraverser::RenderBlock(const TrackOutput *track, const TimeRa NodeValueTable table; if (active_block) { - table = ProcessNode(NodeDependency(active_block, range)); + table = GenerateTable(active_block, range); } return table; @@ -104,19 +117,4 @@ StreamPtr NodeTraverser::ResolveStreamFromInput(NodeInput *input) return input->get_standard_value().value(); } -NodeValueTable NodeTraverser::ProcessInput(const NodeInput *input, const TimeRange& range) -{ - if (input->IsConnected()) { - // Value will equal something from the connected node, follow it - return ProcessNode(NodeDependency(input->get_connected_node(), range)); - } else { - // Push onto the table the value at this time from the input - QVariant input_value = input->get_value_at_time(range.in()); - - NodeValueTable table; - table.Push(input->data_type(), input_value); - return table; - } -} - OLIVE_NAMESPACE_EXIT diff --git a/app/node/traverser.h b/app/node/traverser.h index 4e80a2a68..2706e193d 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -23,7 +23,6 @@ #include "codec/decoder.h" #include "common/cancelableobject.h" -#include "dependency.h" #include "node/output/track/track.h" #include "project/item/footage/stream.h" #include "value.h" @@ -35,21 +34,23 @@ class NodeTraverser : public CancelableObject public: NodeTraverser() = default; - NodeValueTable ProcessNode(const NodeDependency &dep); + NodeValueTable GenerateTable(const Node *n, const TimeRange &range) const; + NodeValueTable GenerateTable(const Node *n, const rational &in, const rational& out) const; - NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range); + NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range) const; protected: - virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range); + virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange& range) const; - NodeValueTable ProcessInput(const NodeInput* input, const TimeRange &range); + virtual void FootageProcessingEvent(StreamPtr, const TimeRange&, NodeValueTable*) const {} - virtual void FootageProcessingEvent(StreamPtr, const TimeRange&, NodeValueTable*){} - - virtual void ProcessNodeEvent(const Node*, const TimeRange&, NodeValueDatabase&, NodeValueTable&){} + virtual void ProcessNodeEvent(const Node*, + const TimeRange&, + NodeValueDatabase&, + NodeValueTable&) const {} private: - StreamPtr ResolveStreamFromInput(NodeInput* input); + static StreamPtr ResolveStreamFromInput(NodeInput* input); }; diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp index fd1a066c5..13a8c7394 100644 --- a/app/panel/viewer/viewerbase.cpp +++ b/app/panel/viewer/viewerbase.cpp @@ -72,11 +72,6 @@ void ViewerPanelBase::DisconnectTimeBasedPanel(TimeBasedPanel *panel) disconnect(panel, &TimeBasedPanel::ShuttleRightRequested, this, &ViewerPanelBase::ShuttleRight); } -VideoRenderBackend *ViewerPanelBase::video_renderer() const -{ - return static_cast(GetTimeBasedWidget())->video_renderer(); -} - void ViewerPanelBase::ConnectPixelSamplerPanel(PixelSamplerPanel *psp) { ViewerWidget* vw = static_cast(GetTimeBasedWidget()); diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index 99340a983..a3261da50 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -47,8 +47,6 @@ public: void DisconnectTimeBasedPanel(TimeBasedPanel* panel); - VideoRenderBackend* video_renderer() const; - void ConnectPixelSamplerPanel(PixelSamplerPanel *psp); /** diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index b87a35925..2f526857b 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -193,7 +193,7 @@ QIcon Sequence::icon() QString Sequence::duration() { - rational timeline_length = viewer_output_->Length(); + rational timeline_length = viewer_output_->GetLength(); int64_t timestamp = Timecode::time_to_timestamp(timeline_length, video_params().time_base()); diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 4a8eec5ad..5bcf50787 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -21,6 +21,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} render/audioparams.h render/audioparams.cpp + render/audioplaybackcache.h + render/audioplaybackcache.cpp render/color.h render/color.cpp render/colormanager.h @@ -29,10 +31,14 @@ set(OLIVE_SOURCES render/colorprocessor.cpp render/diskmanager.h render/diskmanager.cpp + render/framehashcache.h + render/framehashcache.cpp render/managedcolor.h render/managedcolor.cpp render/pixelformat.h render/pixelformat.cpp + render/playbackcache.h + render/playbackcache.cpp render/rendermodes.h render/videoparams.h render/videoparams.cpp diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp new file mode 100644 index 000000000..8be335067 --- /dev/null +++ b/app/render/audioplaybackcache.cpp @@ -0,0 +1,56 @@ +/*** + + 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 "audioplaybackcache.h" + +#include +#include + +#include "common/filefunctions.h" + +OLIVE_NAMESPACE_ENTER + +AudioPlaybackCache::AudioPlaybackCache() +{ + // FIXME: We just use a randomly generated number, one day this should be matchable to the + // project so it can be reused. + quint32 r = QRandomGenerator::global()->generate(); + filename_ = QDir(FileFunctions::GetMediaCacheLocation()).filePath(QString::number(r)); + filename_.append(QStringLiteral(".pcm")); +} + +void AudioPlaybackCache::SetParameters(const AudioRenderingParams ¶ms) +{ + if (params_ == params) { + return; + } + + // Our current audio cache is unusable, so we truncate it automatically + InvalidateAll(); + + emit ParametersChanged(); +} + +const QString &AudioPlaybackCache::GetCacheFilename() const +{ + return filename_; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/node/dependency.h b/app/render/audioplaybackcache.h similarity index 59% rename from app/node/dependency.h rename to app/render/audioplaybackcache.h index 8164a4ad0..bffffb0aa 100644 --- a/app/node/dependency.h +++ b/app/render/audioplaybackcache.h @@ -18,35 +18,41 @@ ***/ -#ifndef NODEDEPENDENCY_H -#define NODEDEPENDENCY_H - -#include +#ifndef AUDIOPLAYBACKCACHE_H +#define AUDIOPLAYBACKCACHE_H #include "common/timerange.h" +#include "codec/samplebuffer.h" +#include "render/playbackcache.h" OLIVE_NAMESPACE_ENTER -class Node; - -class NodeDependency { +class AudioPlaybackCache : public PlaybackCache +{ + Q_OBJECT public: - NodeDependency(); - NodeDependency(const Node* node, const TimeRange& range); - NodeDependency(const Node* node, const rational& in, const rational &out); + AudioPlaybackCache(); - const Node* node() const; - const rational& in() const; - const rational& out() const; - const TimeRange& range() const; + const AudioRenderingParams& GetParameters() const { + return params_; + } + + void SetParameters(const AudioRenderingParams& params); + + void WritePCM(SampleBufferPtr samples); + + const QString& GetCacheFilename() const; + +signals: + void ParametersChanged(); private: - const Node* node_; - TimeRange range_; + QString filename_; + + AudioRenderingParams params_; + }; OLIVE_NAMESPACE_EXIT -Q_DECLARE_METATYPE(OLIVE_NAMESPACE::NodeDependency) - -#endif // NODEDEPENDENCY_H +#endif // AUDIOPLAYBACKCACHE_H diff --git a/app/render/backend/CMakeLists.txt b/app/render/backend/CMakeLists.txt index d191f51a0..adc39f19c 100644 --- a/app/render/backend/CMakeLists.txt +++ b/app/render/backend/CMakeLists.txt @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -add_subdirectory(audio) add_subdirectory(opengl) set(OLIVE_SOURCES @@ -27,20 +26,6 @@ set(OLIVE_SOURCES render/backend/renderbackend.h render/backend/renderbackend.cpp - render/backend/renderworker.h - render/backend/renderworker.cpp - - render/backend/audiorenderbackend.h - render/backend/audiorenderbackend.cpp - render/backend/audiorenderworker.h - render/backend/audiorenderworker.cpp - - render/backend/videorenderbackend.h - render/backend/videorenderbackend.cpp - render/backend/videorenderframecache.h - render/backend/videorenderframecache.cpp - render/backend/videorenderworker.h - render/backend/videorenderworker.cpp render/backend/rendercache.h render/backend/colorprocessorcache.h diff --git a/app/render/backend/audio/CMakeLists.txt b/app/render/backend/audio/CMakeLists.txt deleted file mode 100644 index 85ec7b702..000000000 --- a/app/render/backend/audio/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -# 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 . - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - render/backend/audio/audiobackend.h - render/backend/audio/audiobackend.cpp - render/backend/audio/audioworker.h - render/backend/audio/audioworker.cpp - PARENT_SCOPE -) diff --git a/app/render/backend/audio/audiobackend.cpp b/app/render/backend/audio/audiobackend.cpp deleted file mode 100644 index f7450542a..000000000 --- a/app/render/backend/audio/audiobackend.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/*** - - 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 "audiobackend.h" - -#include "audioworker.h" - -OLIVE_NAMESPACE_ENTER - -AudioBackend::AudioBackend(QObject *parent) : - AudioRenderBackend(parent) -{ -} - -AudioBackend::~AudioBackend() -{ - Close(); -} - -bool AudioBackend::InitInternal() -{ - // Initiate one thread per CPU core - for (int i=0;iSetParameters(params()); - processors_.append(processor); - } - - return true; -} - -void AudioBackend::CloseInternal() -{ -} - -void AudioBackend::ConnectWorkerToThis(RenderWorker *worker) -{ - AudioRenderBackend::ConnectWorkerToThis(worker); - - connect(worker, &RenderWorker::CompletedCache, this, &AudioBackend::ThreadCompletedCache); -} - -void AudioBackend::ThreadCompletedCache(NodeDependency dep, NodeValueTable data, qint64 job_time) -{ - SetWorkerBusyState(static_cast(sender()), false); - - if (job_time == render_job_info_.value(dep.range())) { - render_job_info_.remove(dep.range()); - - SampleBufferPtr cached_sample_ptr = data.Get(NodeParam::kSamples).value(); - - if (cached_sample_ptr) { - QByteArray cached_samples = cached_sample_ptr->toPackedData(); - - int offset = params().time_to_bytes(dep.in()); - int length = params().time_to_bytes(dep.range().length()); - int out_point = qMin(offset + length, params().time_to_bytes(GetSequenceLength())); - - if (offset < out_point) { - if (offset + length > out_point) { - length = out_point - offset; - } - - QFile f(CachePathName()); - if (f.open(QFile::ReadWrite)) { - - if (f.size() < out_point && !f.resize(out_point)) { - qCritical() << "Failed to resize file" << CachePathName(); - } - - if (!f.seek(offset)) { - qCritical() << "Failed to seek file" << CachePathName(); - } - - // Replace data with this data - int copy_length = qMin(length, cached_samples.size()); - - f.write(cached_samples.data(), copy_length); - - if (copy_length < length) { - - // Fill in remainder with silence - QByteArray empty_space(length - copy_length, 0); - f.write(empty_space); - } - - f.close(); - } else { - qWarning() << "Failed to write to cached PCM file"; - } - } - } - } - - CacheNext(); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/audio/audiobackend.h b/app/render/backend/audio/audiobackend.h deleted file mode 100644 index 1eaa2941d..000000000 --- a/app/render/backend/audio/audiobackend.h +++ /dev/null @@ -1,52 +0,0 @@ -/*** - - 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 AUDIOBACKEND_H -#define AUDIOBACKEND_H - -#include - -#include "../audiorenderbackend.h" - -OLIVE_NAMESPACE_ENTER - -class AudioBackend : public AudioRenderBackend -{ - Q_OBJECT -public: - AudioBackend(QObject* parent = nullptr); - - virtual ~AudioBackend() override; - -protected: - virtual bool InitInternal() override; - - virtual void CloseInternal() override; - - virtual void ConnectWorkerToThis(RenderWorker* worker) override; - -private slots: - void ThreadCompletedCache(NodeDependency dep, NodeValueTable data, qint64 job_time); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // AUDIOBACKEND_H diff --git a/app/render/backend/audio/audioworker.cpp b/app/render/backend/audio/audioworker.cpp deleted file mode 100644 index 8c00e099d..000000000 --- a/app/render/backend/audio/audioworker.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/*** - - 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 "audioworker.h" - -OLIVE_NAMESPACE_ENTER - -AudioWorker::AudioWorker(QHash *copy_map, QObject *parent) : - AudioRenderWorker(copy_map, parent) -{ -} - -NodeValue AudioWorker::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) -{ - if (decoder->HasConformedVersion(audio_params())) { - SampleBufferPtr frame = decoder->RetrieveAudio(range.in(), range.out() - range.in(), audio_params()); - - if (frame) { - return NodeValue(NodeParam::kSamples, QVariant::fromValue(frame)); - } - } else { - emit ConformUnavailable(decoder->stream(), CurrentPath().range(), range.out(), audio_params()); - } - - return NodeValue(); -} - -void AudioWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in, NodeValueTable &output_params) -{ - // Check if node processes samples - if (!(node->GetCapabilities(input_params_in) & Node::kSampleProcessor)) { - return; - } - - // Copy database so we can make some temporary modifications to it - NodeValueDatabase input_params = input_params_in; - NodeInput* sample_input = node->ProcessesSamplesFrom(input_params); - - // Try to find the sample buffer in the table - QVariant samples_var = input_params[sample_input].Get(NodeParam::kSamples); - - // If there isn't one, there's nothing to do - if (samples_var.isNull()) { - return; - } - - SampleBufferPtr input_buffer = samples_var.value(); - - if (!input_buffer) { - return; - } - - SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(input_buffer->audio_params(), input_buffer->sample_count_per_channel()); - - int sample_count = input_buffer->sample_count_per_channel(); - - // FIXME: Hardcoded float sample format - for (int i=0;i(sample_out_of_channel) / static_cast(audio_params().sample_rate()); - - rational this_sample_time = rational::fromDouble(range.in().toDouble() + sample_to_second); - - // Update all non-sample and non-footage inputs - foreach (NodeParam* param, node->parameters()) { - if (param->type() == NodeParam::kInput - && param != sample_input) { - NodeInput* input = static_cast(param); - - // If the input isn't keyframing, we don't need to update it unless it's connected, in which case it may change - if (input->IsConnected() || input->is_keyframing()) { - input_params.Insert(input, ProcessInput(input, TimeRange(this_sample_time, this_sample_time))); - } - } - } - - node->ProcessSamples(input_params, - audio_params(), - input_buffer, - output_buffer, - i); - } - - output_params.Push(NodeParam::kSamples, QVariant::fromValue(output_buffer)); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/audiorenderbackend.cpp b/app/render/backend/audiorenderbackend.cpp deleted file mode 100644 index 9e819c0c8..000000000 --- a/app/render/backend/audiorenderbackend.cpp +++ /dev/null @@ -1,275 +0,0 @@ -/*** - - 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 "audiorenderbackend.h" - -#include -#include - -#include "audiorenderworker.h" -#include "common/filefunctions.h" -#include "task/conform/conform.h" -#include "task/taskmanager.h" - -OLIVE_NAMESPACE_ENTER - -AudioRenderBackend::AudioRenderBackend(QObject *parent) : - RenderBackend(parent), - ic_from_conform_(false) -{ - connect(this, &AudioRenderBackend::QueueComplete, this, &AudioRenderBackend::FilterQueueCompleteSignal); -} - -void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms) -{ - CancelQueue(); - - // Set new parameters - params_ = params; - - // Set params on all processors - foreach (RenderWorker* worker, processors_) { - static_cast(worker)->SetParameters(params_); - } - - // Regenerate the cache ID - RegenerateCacheID(); - - emit ParamsChanged(); -} - -void AudioRenderBackend::ConnectViewer(ViewerOutput *node) -{ - connect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache); - connect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache); -} - -void AudioRenderBackend::DisconnectViewer(ViewerOutput *node) -{ - disconnect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache); - disconnect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache); - - conform_wait_info_.clear(); -} - -bool AudioRenderBackend::GenerateCacheIDInternal(QCryptographicHash &hash) -{ - if (!params_.is_valid()) { - return false; - } - - // Generate an ID that is more or less guaranteed to be unique to this Sequence - hash.addData(QString::number(params_.sample_rate()).toUtf8()); - hash.addData(QString::number(params_.channel_layout()).toUtf8()); - hash.addData(QString::number(params_.format()).toUtf8()); - - return true; -} - -const AudioRenderingParams &AudioRenderBackend::params() const -{ - return params_; -} - -NodeInput *AudioRenderBackend::GetDependentInput() -{ - return viewer_node()->samples_input(); -} - -QString AudioRenderBackend::CachePathName() const -{ - QString cache_fn = cache_id(); - cache_fn.append(".pcm"); - return QDir(FileFunctions::GetMediaCacheLocation()).filePath(cache_fn); -} - -bool AudioRenderBackend::CanRender() -{ - return params_.is_valid(); -} - -void AudioRenderBackend::ConnectWorkerToThis(RenderWorker *worker) -{ - AudioRenderWorker* arw = static_cast(worker); - - connect(arw, &AudioRenderWorker::ConformUnavailable, this, &AudioRenderBackend::ConformUnavailable, Qt::QueuedConnection); -} - -TimeRange AudioRenderBackend::PopNextFrameFromQueue() -{ - TimeRange range = cache_queue_.first(); - - // Limit range per worker to 2 seconds (FIXME: arbitrary, should be tweaked, maybe even in config?) - range.set_out(qMin(range.out(), range.in() + rational(2))); - - cache_queue_.RemoveTimeRange(range); - - return range; -} - -void AudioRenderBackend::InvalidateCacheInternal(const rational &start_range, const rational &end_range) -{ - if (!ic_from_conform_) { - // Cancel any ranges waiting on a conform here since obviously the contents have changed - TimeRange range(start_range, end_range); - - for (int i=0;i start_range) { - info.affected_range.set_out(start_range); - } else if (info.affected_range.in() < end_range && info.affected_range.out() > end_range) { - info.affected_range.set_in(end_range); - } - } - } - - RenderBackend::InvalidateCacheInternal(start_range, end_range); -} - -void AudioRenderBackend::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, &AudioRenderBackend::ConformUpdated); -} - -void AudioRenderBackend::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, &AudioRenderBackend::ConformUpdated); -} - -void AudioRenderBackend::ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params) -{ - ConformWaitInfo info = {stream, params, range, stream_time}; - - if (conform_wait_info_.contains(info)) { - 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; - InvalidateCache(range, nullptr); - 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 AudioRenderBackend::ConformUpdated(AudioRenderingParams params) -{ - AudioStream *stream = static_cast(sender()); - - for (int i=0;i seq_length) { - cache_pcm.resize(seq_length); - } -} - -void AudioRenderBackend::FilterQueueCompleteSignal() -{ - if (conform_wait_info_.isEmpty()) { - emit AudioComplete(); - } -} - -bool AudioRenderBackend::ConformWaitInfo::operator==(const AudioRenderBackend::ConformWaitInfo &rhs) const -{ - return rhs.params == params - && rhs.stream == stream - && rhs.stream_time == stream_time - && rhs.affected_range == affected_range; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/audiorenderbackend.h b/app/render/backend/audiorenderbackend.h deleted file mode 100644 index ef6e6a090..000000000 --- a/app/render/backend/audiorenderbackend.h +++ /dev/null @@ -1,105 +0,0 @@ -/*** - - 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 AUDIORENDERBACKEND_H -#define AUDIORENDERBACKEND_H - -#include "common/timerange.h" -#include "renderbackend.h" - -OLIVE_NAMESPACE_ENTER - -class AudioRenderBackend : public RenderBackend -{ - Q_OBJECT -public: - AudioRenderBackend(QObject* parent = nullptr); - - /** - * @brief Set parameters of the Renderer - * - * The Renderer owns the buffers that are used in the rendering process and this function sets the kind of buffers - * to use. The Renderer must be stopped when calling this function. - */ - void SetParameters(const AudioRenderingParams ¶ms); - - const AudioRenderingParams& params() const; - - QString CachePathName() const; - -signals: - void ParamsChanged(); - - void AudioComplete(); - -protected: - virtual void ConnectViewer(ViewerOutput* node) override; - - virtual void DisconnectViewer(ViewerOutput* node) override; - - /** - * @brief Internal function for generating the cache ID - */ - virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) override; - - virtual NodeInput* GetDependentInput() override; - - virtual bool CanRender() override; - - virtual void ConnectWorkerToThis(RenderWorker* worker) override; - - virtual TimeRange PopNextFrameFromQueue() override; - - virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range) override; - -private: - struct ConformWaitInfo { - StreamPtr stream; - AudioRenderingParams params; - TimeRange affected_range; - rational stream_time; - - bool operator==(const ConformWaitInfo& rhs) const; - }; - - void ListenForConformSignal(AudioStreamPtr s); - - void StopListeningForConformSignal(AudioStream *s); - - QList conform_wait_info_; - - AudioRenderingParams params_; - - bool ic_from_conform_; - -private slots: - void ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params); - - void ConformUpdated(OLIVE_NAMESPACE::AudioRenderingParams params); - - void TruncateCache(const rational& r); - - void FilterQueueCompleteSignal(); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // AUDIORENDERBACKEND_H diff --git a/app/render/backend/audiorenderworker.cpp b/app/render/backend/audiorenderworker.cpp deleted file mode 100644 index 053a4c6f0..000000000 --- a/app/render/backend/audiorenderworker.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/*** - - 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 "audiorenderworker.h" - -#include -#include - -#include "audio/audiomanager.h" -#include "audio/sumsamples.h" -#include "config/config.h" -#include "node/block/clip/clip.h" - -OLIVE_NAMESPACE_ENTER - -AudioRenderWorker::AudioRenderWorker(QHash *copy_map, QObject *parent) : - RenderWorker(parent), - copy_map_(copy_map) -{ -} - -void AudioRenderWorker::SetParameters(const AudioRenderingParams &audio_params) -{ - audio_params_ = audio_params; -} - -bool AudioRenderWorker::InitInternal() -{ - // Nothing to init yet - return true; -} - -void AudioRenderWorker::CloseInternal() -{ - // Nothing to init yet -} - -NodeValueTable AudioRenderWorker::RenderBlock(const TrackOutput *track, const TimeRange &range) -{ - QList active_blocks = track->BlocksAtTimeRange(range); - - // All these blocks will need to output to a buffer so we create one here - SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params_, audio_params_.time_to_samples(range.length())); - block_range_buffer->fill(0); - - NodeValueTable merged_table; - - // Loop through active blocks retrieving their audio - foreach (Block* b, active_blocks) { - TimeRange range_for_block(qMax(b->in(), range.in()), - qMin(b->out(), range.out())); - - int destination_offset = audio_params_.time_to_samples(range_for_block.in() - range.in()); - int max_dest_sz = audio_params_.time_to_samples(range_for_block.length()); - - // Destination buffer - NodeValueTable table = ProcessNode(NodeDependency(b, range_for_block)); - QVariant sample_val = table.Take(NodeParam::kSamples); - SampleBufferPtr samples_from_this_block; - - if (sample_val.isNull() - || !(samples_from_this_block = sample_val.value())) { - // If we retrieved no samples from this block, do nothing - continue; - } - - // Stretch samples here - rational abs_speed = qAbs(b->speed()); - - if (abs_speed != 1) { - samples_from_this_block->speed(abs_speed.toDouble()); - } - - if (b->is_reversed()) { - // Reverse the audio buffer - samples_from_this_block->reverse(); - } - - int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count_per_channel()); - - // Copy samples into destination buffer - block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length); - - { - // Save waveform to file - 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("."); - QString wave_fn(waveform_loc.filePath(QString::number(reinterpret_cast(src_block)))); - QFile wave_file(wave_fn); - - if (wave_file.open(QFile::ReadWrite)) { - // We use S32 as a size-compatible substitute for SampleSummer::Sum which is 4 bytes in size - AudioRenderingParams waveform_params(SampleSummer::kSumSampleRate, audio_params_.channel_layout(), SampleFormat::SAMPLE_FMT_S32); - int chunk_size = (audio_params().sample_rate() / waveform_params.sample_rate()); - - { - // Write metadata header - SampleSummer::Info info; - info.channels = audio_params_.channel_count(); - wave_file.write(reinterpret_cast(&info), sizeof(SampleSummer::Info)); - } - - qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in()); - qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length()); - qint64 end_offset = start_offset + length_offset; - - if (wave_file.size() < end_offset) { - wave_file.resize(end_offset); - } - - wave_file.seek(start_offset); - - for (int i=0;isample_count_per_channel();i+=chunk_size) { - QVector summary = SampleSummer::SumSamples(samples_from_this_block, - i, - qMin(chunk_size, samples_from_this_block->sample_count_per_channel() - i)); - - wave_file.write(reinterpret_cast(summary.constData()), - summary.size() * sizeof(SampleSummer::Sum)); - } - - wave_file.close(); - - if (src_block->type() == Block::kClip) { - emit static_cast(src_block)->PreviewUpdated(); - } - } - } - - NodeValueTable::Merge({merged_table, table}); - } - - merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer)); - - return merged_table; -} - -void AudioRenderWorker::FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable *table) -{ - if (stream->type() != Stream::kAudio) { - return; - } - - NodeValue value = GetDataFromStream(stream, input_time); - - table->Push(value); -} - -const AudioRenderingParams &AudioRenderWorker::audio_params() const -{ - return audio_params_; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/audiorenderworker.h b/app/render/backend/audiorenderworker.h deleted file mode 100644 index 29c1f3f52..000000000 --- a/app/render/backend/audiorenderworker.h +++ /dev/null @@ -1,59 +0,0 @@ -/*** - - 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 AUDIORENDERWORKER_H -#define AUDIORENDERWORKER_H - -#include "renderworker.h" - -OLIVE_NAMESPACE_ENTER - -class AudioRenderWorker : public RenderWorker -{ - Q_OBJECT -public: - AudioRenderWorker(QHash* copy_map, QObject* parent = nullptr); - - void SetParameters(const AudioRenderingParams& audio_params); - -signals: - void ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params); - -protected: - virtual bool InitInternal() override; - - virtual void CloseInternal() override; - - virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range) override; - - virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) override; - - const AudioRenderingParams& audio_params() const; - -private: - AudioRenderingParams audio_params_; - - QHash* copy_map_; - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // AUDIORENDERWORKER_H diff --git a/app/render/backend/exporter.cpp b/app/render/backend/exporter.cpp index 5ec95611c..bf3cefeff 100644 --- a/app/render/backend/exporter.cpp +++ b/app/render/backend/exporter.cpp @@ -22,7 +22,6 @@ #include -#include "render/backend/audio/audiobackend.h" #include "render/backend/opengl/openglbackend.h" #include "render/colormanager.h" #include "render/pixelformat.h" @@ -36,8 +35,7 @@ Exporter::Exporter(ViewerOutput *viewer_node, QObject(parent), viewer_node_(viewer_node), params_(params), - video_backend_(nullptr), - audio_backend_(nullptr), + renderer_(nullptr), export_status_(false), export_msg_(tr("Export hasn't started yet")) { @@ -54,7 +52,7 @@ Exporter::Exporter(ViewerOutput *viewer_node, if (params_.has_custom_range()) { export_range_ = params_.custom_range(); } else { - export_range_ = TimeRange(0, viewer_node_->Length()); + export_range_ = TimeRange(0, viewer_node_->GetLength()); } if (params_.video_enabled()) { @@ -87,18 +85,10 @@ const QString &Exporter::GetExportError() const void Exporter::Cancel() { - if (video_backend_) { - video_backend_->CancelQueue(); - video_backend_->Close(); - video_backend_->deleteLater(); - video_backend_ = nullptr; - } - - if (audio_backend_) { - audio_backend_->CancelQueue(); - audio_backend_->Close(); - audio_backend_->deleteLater(); - audio_backend_ = nullptr; + if (renderer_) { + renderer_->CancelQueue(); + renderer_->deleteLater(); + renderer_ = nullptr; } SetExportMessage(tr("User cancelled export")); @@ -111,25 +101,18 @@ void Exporter::StartExporting() export_status_ = false; // Create renderers - if (!video_done_) { - video_backend_ = new OpenGLBackend(); + renderer_ = new OpenGLBackend(); + renderer_->SetViewerNode(viewer_node_); - video_backend_->SetLimitCaching(false); - video_backend_->SetViewerNode(viewer_node_); - video_backend_->SetParameters(VideoRenderingParams(viewer_node_->video_params().width(), - viewer_node_->video_params().height(), - params_.video_params().time_base(), - params_.video_params().format(), - params_.video_params().mode())); + if (!video_done_) { + renderer_->SetPixelFormat(params_.video_params().format()); + renderer_->SetMode(params_.video_params().mode()); waiting_for_frame_ = 0; } if (!audio_done_) { - audio_backend_ = new AudioBackend(); - - audio_backend_->SetViewerNode(viewer_node_); - audio_backend_->SetParameters(params_.audio_params()); + renderer_->SetSampleFormat(params_.audio_params().format()); } // Open encoder and wait for result @@ -153,10 +136,9 @@ void Exporter::ExportSucceeded() return; } - if (video_backend_) { - video_backend_->Close(); - video_backend_->deleteLater(); - video_backend_ = nullptr; + if (renderer_) { + renderer_->deleteLater(); + renderer_ = nullptr; } export_status_ = true; @@ -189,10 +171,10 @@ void Exporter::EncodeFrame() waiting_for_frame_ += params_.video_params().time_base(); // Calculate progress - emit ProgressChanged(waiting_for_frame_.toDouble() / viewer_node_->Length().toDouble()); + emit ProgressChanged(waiting_for_frame_.toDouble() / viewer_node_->GetLength().toDouble()); } - if (waiting_for_frame_ >= viewer_node_->Length()) { + if (waiting_for_frame_ >= viewer_node_->GetLength()) { video_done_ = true; debug_timer_.stop(); @@ -261,6 +243,7 @@ void Exporter::FrameRendered(FramePtr frame) void Exporter::AudioRendered() { + /* // Retrieve the audio filename QString cache_fn = audio_backend_->CachePathName(); @@ -270,11 +253,7 @@ void Exporter::AudioRendered() OLIVE_NS_ARG(AudioRenderingParams, audio_backend_->params()), Q_ARG(const QString&, cache_fn), OLIVE_NS_ARG(TimeRange, export_range_)); - - // We don't need the audio backend anymorea - audio_backend_->Close(); - audio_backend_->deleteLater(); - audio_backend_ = nullptr; + */ } void Exporter::AudioEncodeComplete() @@ -286,6 +265,7 @@ void Exporter::AudioEncodeComplete() void Exporter::EncoderOpenedSuccessfully() { + /* // Invalidate caches if (!video_done_) { // First we generate the hashes so we know exactly how many frames we need @@ -301,6 +281,7 @@ void Exporter::EncoderOpenedSuccessfully() audio_backend_->InvalidateCache(export_range_, nullptr); } + */ } void Exporter::EncoderOpenFailed() @@ -317,12 +298,13 @@ void Exporter::EncoderClosed() void Exporter::VideoHashesComplete() { + /* // We've got our hashes, time to kick off actual rendering disconnect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete); // Determine what frames will be hashed TimeRangeList ranges; - ranges.append(TimeRange(0, viewer_node_->Length())); + ranges.append(TimeRange(0, viewer_node_->GetLength())); // Set video backend to render mode but NOT hash or download video_backend_->SetOperatingMode(VideoRenderWorker::kRenderOnly); @@ -347,6 +329,7 @@ void Exporter::VideoHashesComplete() foreach (const TimeRange& range, ranges) { video_backend_->InvalidateCache(range, nullptr); } + */ } void Exporter::DebugTimerMessage() @@ -356,7 +339,7 @@ void Exporter::DebugTimerMessage() void Exporter::FrameColorFinished() { - if (!video_backend_ && !audio_backend_) { + if (!renderer_) { return; } @@ -366,7 +349,7 @@ void Exporter::FrameColorFinished() debug_timer_.stop(); - const QMap& time_hash_map = video_backend_->frame_cache()->time_hash_map(); + const QMap& time_hash_map = viewer_node_->video_frame_cache()->time_hash_map(); QByteArray this_hash = time_hash_map.value(frame->timestamp()); diff --git a/app/render/backend/exporter.h b/app/render/backend/exporter.h index 6c07a0dcd..94380aeb2 100644 --- a/app/render/backend/exporter.h +++ b/app/render/backend/exporter.h @@ -28,9 +28,8 @@ #include "codec/encoder.h" #include "node/output/viewer/viewer.h" -#include "render/backend/audiorenderbackend.h" #include "render/backend/exportparams.h" -#include "render/backend/videorenderbackend.h" +#include "render/backend/renderbackend.h" #include "render/colorprocessor.h" OLIVE_NAMESPACE_ENTER @@ -76,8 +75,7 @@ private: ExportParams params_; // Renderers - VideoRenderBackend* video_backend_; - AudioRenderBackend* audio_backend_; + RenderBackend* renderer_; // Export transform QMatrix4x4 transform_; diff --git a/app/render/backend/opengl/CMakeLists.txt b/app/render/backend/opengl/CMakeLists.txt index e2972b85f..75af58e48 100644 --- a/app/render/backend/opengl/CMakeLists.txt +++ b/app/render/backend/opengl/CMakeLists.txt @@ -33,7 +33,5 @@ set(OLIVE_SOURCES render/backend/opengl/opengltexture.cpp render/backend/opengl/opengltexturecache.h render/backend/opengl/opengltexturecache.cpp - render/backend/opengl/openglworker.h - render/backend/opengl/openglworker.cpp PARENT_SCOPE ) diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index 2f996e82c..7370537c5 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -20,77 +20,12 @@ #include "openglbackend.h" -#include -#include - -#include "openglrenderfunctions.h" - OLIVE_NAMESPACE_ENTER -OpenGLBackend::OpenGLBackend(QObject *parent) : - VideoRenderBackend(parent), - proxy_(nullptr) +OpenGLBackend::OpenGLBackend(QObject* parent) : + RenderBackend(parent) { -} -OpenGLBackend::~OpenGLBackend() -{ - Close(); -} - -bool OpenGLBackend::InitInternal() -{ - if (!VideoRenderBackend::InitInternal()) { - return false; - } - - proxy_ = new OpenGLProxy(); - proxy_->SetParameters(params()); - QThread* proxy_thread = new QThread(); - proxy_thread->start(QThread::IdlePriority); - proxy_->moveToThread(proxy_thread); - - if (!proxy_->Init()) { - proxy_thread->quit(); - proxy_thread->wait(); - proxy_thread->deleteLater(); - - proxy_->deleteLater(); - proxy_ = nullptr; - return false; - } - - // Initiate one thread per CPU core - for (int i=0;iSetParameters(params()); - processors_.append(processor); - } - - return true; -} - -void OpenGLBackend::CloseInternal() -{ - if (proxy_) { - proxy_->thread()->quit(); - proxy_->thread()->wait(); - proxy_->thread()->deleteLater(); - - proxy_->deleteLater(); - proxy_ = nullptr; - } - - VideoRenderBackend::CloseInternal(); -} - -void OpenGLBackend::ParamsChangedEvent() -{ - // If we're initiated, we need to recreate the texture. Otherwise this backend isn't active so it doesn't matter. - if (IsInitiated()) { - proxy_->SetParameters(params()); - } } OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/backend/opengl/openglbackend.h index 62945ffa1..6852fac93 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/backend/opengl/openglbackend.h @@ -21,32 +21,21 @@ #ifndef OPENGLBACKEND_H #define OPENGLBACKEND_H -#include "../videorenderbackend.h" -#include "openglframebuffer.h" -#include "openglproxy.h" -#include "openglshader.h" -#include "opengltexture.h" -#include "openglworker.h" +#include "render/backend/renderbackend.h" OLIVE_NAMESPACE_ENTER -class OpenGLBackend : public VideoRenderBackend +class OpenGLBackend : public RenderBackend { - Q_OBJECT public: OpenGLBackend(QObject* parent = nullptr); - virtual ~OpenGLBackend() override; - protected: - virtual bool InitInternal() override; + virtual void TextureToFrame(const QVariant& texture, FramePtr frame) const override; - virtual void CloseInternal() override; + virtual QVariant FrameToTexture(FramePtr frame) const override; - virtual void ParamsChangedEvent() override; - -private: - OpenGLProxy* proxy_; + virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) const override; }; diff --git a/app/render/backend/opengl/openglcolorprocessor.h b/app/render/backend/opengl/openglcolorprocessor.h index 85d429c68..0de0589ea 100644 --- a/app/render/backend/opengl/openglcolorprocessor.h +++ b/app/render/backend/opengl/openglcolorprocessor.h @@ -22,6 +22,7 @@ #define OPENGLCOLORPROCESSOR_H #include "openglshader.h" +#include "render/backend/rendercache.h" #include "render/colorprocessor.h" OLIVE_NAMESPACE_ENTER @@ -62,6 +63,8 @@ private slots: }; +using OpenGLColorProcessorCache = RenderCache; + OLIVE_NAMESPACE_EXIT #endif // OPENGLCOLORPROCESSOR_H diff --git a/app/render/backend/opengl/openglproxy.h b/app/render/backend/opengl/openglproxy.h index 23f1f0501..9fc5072b5 100644 --- a/app/render/backend/opengl/openglproxy.h +++ b/app/render/backend/opengl/openglproxy.h @@ -24,7 +24,9 @@ #include #include -#include "../videorenderworker.h" +#include "common/timerange.h" +#include "node/value.h" +#include "openglcolorprocessor.h" #include "openglframebuffer.h" #include "openglshadercache.h" #include "opengltexturecache.h" @@ -81,7 +83,7 @@ private: OpenGLFramebuffer buffer_; - ColorProcessorCache color_cache_; + OpenGLColorProcessorCache color_cache_; VideoRenderingParams video_params_; diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp deleted file mode 100644 index e0d65f242..000000000 --- a/app/render/backend/opengl/openglworker.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/*** - - 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 "openglworker.h" - -#include "common/clamp.h" -#include "core.h" -#include "node/block/transition/transition.h" -#include "node/node.h" -#include "openglcolorprocessor.h" -#include "openglrenderfunctions.h" -#include "render/colormanager.h" -#include "render/pixelformat.h" - -OLIVE_NAMESPACE_ENTER - -OpenGLWorker::OpenGLWorker(VideoRenderFrameCache *frame_cache, OpenGLProxy *proxy, QObject *parent) : - VideoRenderWorker(frame_cache, parent), - proxy_(proxy) -{ -} - -NodeValue OpenGLWorker::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) -{ - FramePtr frame = decoder->RetrieveVideo(range.in(), - video_params().divider(), - video_params().mode() == RenderMode::kOffline); - - NodeValue value; - - if (frame) { - QMetaObject::invokeMethod(proxy_, - "FrameToValue", - Qt::BlockingQueuedConnection, - OLIVE_NS_RETURN_ARG(NodeValue, value), - OLIVE_NS_ARG(FramePtr, frame), - OLIVE_NS_ARG(StreamPtr, stream)); - } - - return value; -} - -void OpenGLWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) -{ - QMetaObject::invokeMethod(proxy_, - "RunNodeAccelerated", - Qt::BlockingQueuedConnection, - OLIVE_NS_CONST_ARG(Node*, node), - OLIVE_NS_CONST_ARG(TimeRange&, range), - OLIVE_NS_ARG(NodeValueDatabase&, input_params), - OLIVE_NS_ARG(NodeValueTable&, output_params)); -} - -void OpenGLWorker::TextureToBuffer(const QVariant &tex_in, int width, int height, const QMatrix4x4& matrix, void *buffer, int linesize) -{ - QMetaObject::invokeMethod(proxy_, - "TextureToBuffer", - Qt::BlockingQueuedConnection, - Q_ARG(const QVariant&, tex_in), - Q_ARG(int, width), - Q_ARG(int, height), - Q_ARG(const QMatrix4x4&, matrix), - Q_ARG(void*, buffer), - Q_ARG(int, linesize)); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h deleted file mode 100644 index da1d469e2..000000000 --- a/app/render/backend/opengl/openglworker.h +++ /dev/null @@ -1,56 +0,0 @@ -/*** - - 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 OPENGLPROCESSOR_H -#define OPENGLPROCESSOR_H - -#include -#include - -#include "../videorenderworker.h" -#include "openglframebuffer.h" -#include "openglproxy.h" -#include "openglshadercache.h" -#include "opengltexturecache.h" - -OLIVE_NAMESPACE_ENTER - -class OpenGLWorker : public VideoRenderWorker { - Q_OBJECT -public: - OpenGLWorker(VideoRenderFrameCache* frame_cache, - OpenGLProxy* proxy, - QObject* parent = nullptr); - -protected: - virtual NodeValue FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) override; - - virtual void RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable& output_params) override; - - virtual void TextureToBuffer(const QVariant& texture, int width, int height, const QMatrix4x4& matrix, void *buffer, int linesize) override; - -private: - OpenGLProxy* proxy_; - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // OPENGLPROCESSOR_H diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 1c1dec7da..3e7164722 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -23,6 +23,7 @@ #include #include +#include "config/config.h" #include "core.h" #include "window/mainwindow/mainwindow.h" @@ -30,331 +31,409 @@ OLIVE_NAMESPACE_ENTER RenderBackend::RenderBackend(QObject *parent) : QObject(parent), - started_(false), viewer_node_(nullptr), - copied_viewer_node_(nullptr) + divider_(1), + render_mode_(RenderMode::kOnline), + pix_fmt_(PixelFormat::PIX_FMT_RGBA32F), + sample_fmt_(SampleFormat::SAMPLE_FMT_FLT) { // FIXME: Don't create in CLI mode cancel_dialog_ = new RenderCancelDialog(Core::instance()->main_window()); } -bool RenderBackend::Init() +void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) { - if (started_) { - return true; - } - - threads_.resize(QThread::idealThreadCount()); - - for (int i=0;istart(QThread::IdlePriority); - } - - cancel_dialog_->SetWorkerCount(threads_.size()); - - started_ = InitInternal(); - - // Connects workers and moves them to their respective threads - InitWorkers(); - - if (!started_) { - Close(); - } - - return started_; -} - -void RenderBackend::Close() -{ - if (!started_) { + if (viewer_node_ == viewer_node) { return; } - started_ = false; - - CancelQueue(); - - SetViewerNode(nullptr); - - CloseInternal(); - - for (int i=0;iquit(); - } - - for (int i=0;iwait(); // FIXME: Maximum time in case a thread is stuck? - delete threads_.at(i); - delete processors_.at(i); - } - - threads_.clear(); - processors_.clear(); -} - -const QString &RenderBackend::GetError() const -{ - return error_; -} - -void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) -{ if (viewer_node_) { + // Clear queue and wait for any currently running actions to complete CancelQueue(); - DisconnectViewer(viewer_node_); + // Delete all of our copied nodes + video_copy_map_.Clear(); + audio_copy_map_.Clear(); - copied_graph_.Clear(); - copied_viewer_node_ = nullptr; - node_copy_map_.clear(); + disconnect(viewer_node_, + &ViewerOutput::GraphChangedFrom, + this, + &RenderBackend::NodeGraphChanged); + + disconnect(viewer_node_->audio_playback_cache(), + &AudioPlaybackCache::Invalidated, + this, + &RenderBackend::AudioCallback); } + // Set viewer node viewer_node_ = viewer_node; if (viewer_node_) { - ConnectViewer(viewer_node_); + // Start copying viewer + video_copy_map_.Init(viewer_node_); + audio_copy_map_.Init(viewer_node_); - RegenerateCacheID(); + video_copy_map_.Queue(viewer_node_->texture_input()); + audio_copy_map_.Queue(viewer_node_->samples_input()); - copied_viewer_node_ = static_cast(viewer_node_->copy()); - copied_graph_.AddNode(copied_viewer_node_); - node_copy_map_.insert(viewer_node_, copied_viewer_node_); + video_copy_map_.ProcessQueue(); + audio_copy_map_.ProcessQueue(); - InvalidateCache(TimeRange(0, RATIONAL_MAX), - static_cast(viewer_node_->GetInputWithID(GetDependentInput()->id()))); + connect(viewer_node_, + &ViewerOutput::GraphChangedFrom, + this, + &RenderBackend::NodeGraphChanged); + + connect(viewer_node_->audio_playback_cache(), + &AudioPlaybackCache::Invalidated, + this, + &RenderBackend::AudioCallback); } } -bool RenderBackend::IsInitiated() -{ - return started_; -} - -void RenderBackend::RegenerateCacheID() -{ - QCryptographicHash hash(QCryptographicHash::Sha1); - - if (!viewer_node_ - || !GenerateCacheIDInternal(hash)) { - cache_id_.clear(); - CacheIDChangedEvent(QString()); - return; - } - - hash.addData(viewer_node_->uuid().toByteArray()); - - QByteArray bytes = hash.result(); - cache_id_ = bytes.toHex(); - CacheIDChangedEvent(cache_id_); -} - -bool RenderBackend::InitInternal() -{ - return true; -} - -void RenderBackend::CloseInternal() -{ -} - -bool RenderBackend::CanRender() -{ - return true; -} - -TimeRange RenderBackend::PopNextFrameFromQueue() -{ - return cache_queue_.takeFirst(); -} - -rational RenderBackend::GetSequenceLength() -{ - if (viewer_node_ == nullptr) { - return 0; - } - - return viewer_node_->Length(); -} - -void RenderBackend::SetError(const QString &error) -{ - error_ = error; -} - -void RenderBackend::ConnectViewer(ViewerOutput *node) -{ - Q_UNUSED(node) -} - -void RenderBackend::DisconnectViewer(ViewerOutput *node) -{ - Q_UNUSED(node) -} - -void RenderBackend::CacheNext() -{ - if (cache_queue_.isEmpty()) { - if (AllProcessorsAreAvailable()) { - emit QueueComplete(); - } - - return; - } - - if (!ViewerIsConnected() - || !CanRender() - || !Init()) { - return; - } - - while (!input_update_queued_.isEmpty()) { - if (!AllProcessorsAreAvailable()) { - // To update the inputs, we need all workers to stop - return; - } - - CopyNodeInputValue(input_update_queued_.takeFirst()); - } - - Node* node_connected_to_viewer = GetDependentInput()->get_connected_node(); - - if (!node_connected_to_viewer) { - return; - } - - foreach (RenderWorker* worker, processors_) { - if (cache_queue_.isEmpty()) { - break; - } - - if (!WorkerIsBusy(worker)) { - TimeRange cache_frame = PopNextFrameFromQueue(); - - NodeDependency dep = NodeDependency(node_connected_to_viewer, - cache_frame); - - // Timestamp this render job - qint64 job_time = QDateTime::currentMSecsSinceEpoch(); - - // Ensure the job's time is unique (since that's the whole point) - // NOTE: This value will be 0 if it doesn't exist, which will never be the result of currentMSecsSinceEpoch so we - // can safely assume 0 means it doesn't exist. - qint64 existing_job_time = render_job_info_.value(cache_frame); - - if (existing_job_time == job_time) { - job_time = existing_job_time + 1; - } - - render_job_info_.insert(cache_frame, job_time); - - SetWorkerBusyState(worker, true); - cancel_dialog_->WorkerStarted(); - - WorkerAboutToStartEvent(worker); - - QMetaObject::invokeMethod(worker, - "Render", - Qt::QueuedConnection, - OLIVE_NS_ARG(NodeDependency, dep), - Q_ARG(qint64, job_time)); - } - } -} - -ViewerOutput *RenderBackend::viewer_node() const -{ - return copied_viewer_node_; -} - void RenderBackend::CancelQueue() { - cache_queue_.clear(); + // FIXME: Implement something better than this... + video_copy_map_.thread_pool()->waitForDone(); + audio_copy_map_.thread_pool()->waitForDone(); +} - int busy = 0; - for (int i=0;i(¶ms.effective_width()), sizeof(int)); + hasher.addData(reinterpret_cast(¶ms.effective_height()), sizeof(int)); + hasher.addData(reinterpret_cast(¶ms.format()), sizeof(PixelFormat::Format)); + hasher.addData(reinterpret_cast(¶ms.mode()), sizeof(RenderMode::Mode)); + + node->Hash(hasher, time); + + return hasher.result(); +} + +QFuture RenderBackend::Hash(const rational &time) +{ + if (!viewer_node_) { + return QFuture(); } - if (busy) { - qDebug() << this << "is waiting for" << busy << "busy workers"; + return QtConcurrent::run(video_copy_map_.thread_pool(), + HashInternal, + viewer_node_, + video_params(), + time); +} + +QFuture RenderBackend::RenderFrame(const rational &time) +{ + if (!viewer_node_) { + return QFuture(); } - cancel_dialog_->RunIfWorkersAreBusy(); + return QtConcurrent::run(video_copy_map_.thread_pool(), + this, + &RenderBackend::RenderFrameInternal, + time); } -void RenderBackend::InvalidateCache(const TimeRange &range, NodeInput *from) +void RenderBackend::SetDivider(const int ÷r) { - // Adjust range to min/max values - rational start_range_adj = qMax(rational(0), range.in()); - rational end_range_adj = qMin(GetSequenceLength(), range.out()); + divider_ = divider; +} - qDebug() << "Cache invalidated between" - << start_range_adj.toDouble() - << "and" - << end_range_adj.toDouble(); +void RenderBackend::SetMode(const RenderMode::Mode &mode) +{ + render_mode_ = mode; +} - if (from) { - // Queue value update - qDebug() << " from" << from->parentNode()->id() << "::" << from->id(); - QueueValueUpdate(from); +void RenderBackend::SetPixelFormat(const PixelFormat::Format &pix_fmt) +{ + pix_fmt_ = pix_fmt; +} + +void RenderBackend::SetSampleFormat(const SampleFormat::Format &sample_fmt) +{ + sample_fmt_ = sample_fmt; +} + +void RenderBackend::NodeGraphChanged(NodeInput *from, NodeInput *source) +{ + if (from == viewer_node_->texture_input()) { + video_copy_map_.Queue(source); + } else if (from == viewer_node_->samples_input()) { + audio_copy_map_.Queue(source); + } +} + +void RenderBackend::FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable *table) const +{ + if (stream->type() == Stream::kVideo || stream->type() == Stream::kImage) { + + ImageStreamPtr video_stream = std::static_pointer_cast(stream); + rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time.in(); + QString colorspace_match = video_stream->get_colorspace_match_string(); + + NodeValue value; + bool found_cache = false; + + if (still_image_cache_.Has(stream.get())) { + CachedStill cs = still_image_cache_.Get(stream.get()); + + if (cs.colorspace == colorspace_match + && cs.alpha_is_associated == video_stream->premultiplied_alpha() + && cs.divider == video_params_.divider() + && cs.time == time_match) { + value = cs.texture; + found_cache = true; + } else { + still_image_cache_.Remove(stream.get()); + } + } + + if (!found_cache) { + + value = GetDataFromStream(stream, input_time); + + still_image_cache_.Add(stream.get(), {value, + colorspace_match, + video_stream->premultiplied_alpha(), + video_params_.divider(), + time_match}); + + } + + table->Push(value); + + } else if (stream->type() != Stream::kAudio) { + + NodeValue value = GetDataFromStream(stream, input_time); + + table->Push(value); + + } +} + +NodeValueTable RenderBackend::GenerateBlockTable(const TrackOutput *track, const TimeRange &range) const +{ + if (track->track_type() == Timeline::kTrackTypeAudio) { + + QList active_blocks = track->BlocksAtTimeRange(range); + + // All these blocks will need to output to a buffer so we create one here + SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params_, + audio_params_.time_to_samples(range.length())); + block_range_buffer->fill(0); + + NodeValueTable merged_table; + + // Loop through active blocks retrieving their audio + foreach (Block* b, active_blocks) { + TimeRange range_for_block(qMax(b->in(), range.in()), + qMin(b->out(), range.out())); + + int destination_offset = audio_params_.time_to_samples(range_for_block.in() - range.in()); + int max_dest_sz = audio_params_.time_to_samples(range_for_block.length()); + + // Destination buffer + NodeValueTable table = ProcessNode(NodeDependency(b, range_for_block)); + QVariant sample_val = table.Take(NodeParam::kSamples); + SampleBufferPtr samples_from_this_block; + + if (sample_val.isNull() + || !(samples_from_this_block = sample_val.value())) { + // If we retrieved no samples from this block, do nothing + continue; + } + + // Stretch samples here + rational abs_speed = qAbs(b->speed()); + + if (abs_speed != 1) { + samples_from_this_block->speed(abs_speed.toDouble()); + } + + if (b->is_reversed()) { + // Reverse the audio buffer + samples_from_this_block->reverse(); + } + + int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count_per_channel()); + + // Copy samples into destination buffer + block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length); + + { + // Save waveform to file + 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("."); + QString wave_fn(waveform_loc.filePath(QString::number(reinterpret_cast(src_block)))); + QFile wave_file(wave_fn); + + if (wave_file.open(QFile::ReadWrite)) { + // We use S32 as a size-compatible substitute for SampleSummer::Sum which is 4 bytes in size + AudioRenderingParams waveform_params(SampleSummer::kSumSampleRate, audio_params_.channel_layout(), SampleFormat::SAMPLE_FMT_S32); + int chunk_size = (audio_params().sample_rate() / waveform_params.sample_rate()); + + { + // Write metadata header + SampleSummer::Info info; + info.channels = audio_params_.channel_count(); + wave_file.write(reinterpret_cast(&info), sizeof(SampleSummer::Info)); + } + + qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in()); + qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length()); + qint64 end_offset = start_offset + length_offset; + + if (wave_file.size() < end_offset) { + wave_file.resize(end_offset); + } + + wave_file.seek(start_offset); + + for (int i=0;isample_count_per_channel();i+=chunk_size) { + QVector summary = SampleSummer::SumSamples(samples_from_this_block, + i, + qMin(chunk_size, samples_from_this_block->sample_count_per_channel() - i)); + + wave_file.write(reinterpret_cast(summary.constData()), + summary.size() * sizeof(SampleSummer::Sum)); + } + + wave_file.close(); + + if (src_block->type() == Block::kClip) { + emit static_cast(src_block)->PreviewUpdated(); + } + } + } + + NodeValueTable::Merge({merged_table, table}); + } + + merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer)); + + return merged_table; + + } else { + return NodeTraverser::GenerateBlockTable(track, range); + } +} + +FramePtr RenderBackend::RenderFrameInternal(const rational &time) const +{ + NodeValueTable table = GenerateTable(viewer_node_, + TimeRange(time, time + viewer_node_->video_params().time_base())); + + QVariant texture = table.Get(NodeParam::kTexture); + + FramePtr frame = Frame::Create(); + frame->set_video_params(video_params()); + frame->allocate(); + + if (texture.isNull()) { + memset(frame->data(), 0, frame->allocated_size()); + } else { + TextureToFrame(texture, frame); } - InvalidateCacheInternal(start_range_adj, end_range_adj); + return frame; } -bool RenderBackend::ViewerIsConnected() const +VideoRenderingParams RenderBackend::video_params() const { - return viewer_node_; + return VideoRenderingParams(viewer_node_->video_params(), + pix_fmt_, + render_mode_, + divider_); } -const QString &RenderBackend::cache_id() const +void RenderBackend::AudioCallback() { - return cache_id_; + qDebug() << "STUB"; + /* + AudioPlaybackCache* pb_cache = viewer_node_->audio_playback_cache(); + QThreadPool* thread_pool = audio_copy_map_.thread_pool(); + + while (!pb_cache->IsFullyValidated() + && thread_pool->activeThreadCount() < thread_pool->maxThreadCount()) { + // FIXME: Trigger a background audio thread + TimeRange r = viewer_node_->audio_playback_cache()->GetInvalidatedRanges().first(); + + qDebug() << "FIXME: Start rendering audio at:" << r; + + break; + } + */ } -void RenderBackend::QueueValueUpdate(NodeInput* from) +bool RenderBackend::ConformWaitInfo::operator==(const RenderBackend::ConformWaitInfo &rhs) const { - if (!input_update_queued_.isEmpty()) { + return rhs.stream == stream + && rhs.stream_time == stream_time + && rhs.affected_range == affected_range; +} + +RenderBackend::CopyMap::CopyMap() : + original_viewer_(nullptr), + copied_viewer_(nullptr) +{ +} + +void RenderBackend::CopyMap::Init(ViewerOutput *viewer) +{ + original_viewer_ = viewer; + + copied_viewer_ = static_cast(original_viewer_->copy()); + copy_map_.insert(original_viewer_, copied_viewer_); +} + +void RenderBackend::CopyMap::Queue(NodeInput *input) +{ + if (!queued_updates_.isEmpty()) { // Remove any inputs that are dependents of this input since they may have been removed since // it was queued - QList deps = from->GetDependencies(); + QList deps = input->GetDependencies(); - for (int i=0;iparentNode())) { + for (int i=0;iparentNode())) { // We don't need to queue this value since this input supersedes it - input_update_queued_.removeAt(i); + queued_updates_.removeAt(i); i--; } } } - input_update_queued_.append(from); + queued_updates_.append(input); } -bool RenderBackend::WorkerIsBusy(RenderWorker *worker) const +void RenderBackend::CopyMap::ProcessQueue() { - return processor_busy_state_.at(processors_.indexOf(worker)); + while (!queued_updates_.isEmpty()) { + CopyNodeInputValue(queued_updates_.takeFirst()); + } } -void RenderBackend::SetWorkerBusyState(RenderWorker *worker, bool busy) +void RenderBackend::CopyMap::Clear() { - processor_busy_state_.replace(processors_.indexOf(worker), busy); + qDeleteAll(copy_map_); + copy_map_.clear(); + + original_viewer_ = nullptr; + copied_viewer_ = nullptr; } -void RenderBackend::CopyNodeInputValue(NodeInput *input) +void RenderBackend::CopyMap::CopyNodeInputValue(NodeInput *input) { // Find our copy of this parameter - Node* our_copy_node = node_copy_map_.value(input->parentNode()); + 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 @@ -372,8 +451,7 @@ void RenderBackend::CopyNodeInputValue(NodeInput *input) QList old_deps = our_copy->GetExclusiveDependencies(); foreach (Node* i, old_deps) { - Node* n = node_copy_map_.take(node_copy_map_.key(i)); - copied_graph_.TakeNode(n); + Node* n = copy_map_.take(copy_map_.key(i)); delete n; } @@ -395,16 +473,15 @@ void RenderBackend::CopyNodeInputValue(NodeInput *input) } } -Node* RenderBackend::CopyNodeConnections(Node* src_node) +Node* RenderBackend::CopyMap::CopyNodeConnections(Node* src_node) { // Check if this node is already in the map - Node* dst_node = node_copy_map_.value(src_node); + Node* dst_node = copy_map_.value(src_node); // If not, create it now if (!dst_node) { dst_node = src_node->copy(); - copied_graph_.AddNode(dst_node); - node_copy_map_.insert(src_node, dst_node); + copy_map_.insert(src_node, dst_node); } // Make sure its values are copied @@ -423,10 +500,8 @@ Node* RenderBackend::CopyNodeConnections(Node* src_node) return dst_node; } -void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_input) +void RenderBackend::CopyMap::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_input) { - //qDebug() << "Copying input" << src_input->id() << "from" << src_input->parentNode()->id(); - if (src_input->IsConnected()) { Node* dst_node = CopyNodeConnections(src_input->get_connected_node()); @@ -437,68 +512,4 @@ void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_ } } -bool RenderBackend::AllProcessorsAreAvailable() const -{ - foreach (bool busy, processor_busy_state_) { - if (busy) { - return false; - } - } - - return true; -} - -const QVector &RenderBackend::threads() -{ - return threads_; -} - -void RenderBackend::InvalidateCacheInternal(const rational &start_range, const rational &end_range) -{ - // Add the range to the list - cache_queue_.InsertTimeRange(TimeRange(start_range, end_range)); - - CacheNext(); -} - -void RenderBackend::CacheIDChangedEvent(const QString &id) -{ - Q_UNUSED(id) -} - -void RenderBackend::WorkerAboutToStartEvent(RenderWorker *worker) -{ - Q_UNUSED(worker) -} - -void RenderBackend::InitWorkers() -{ - for (int i=0;imoveToThread(thread); - - // This function blocks the main thread intentionally. See the documentation for this function to see why. - processor->Init(); - } - - processor_busy_state_.resize(processors_.size()); - processor_busy_state_.fill(false); -} - -bool RenderBackend::FootageWaitInfo::operator==(const RenderBackend::FootageWaitInfo &rhs) const -{ - return rhs.stream == stream - && rhs.stream_time == stream_time - && rhs.affected_range == affected_range; -} - OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index 4e53f7758..ed2da09ce 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -21,153 +21,126 @@ #ifndef RENDERBACKEND_H #define RENDERBACKEND_H -#include +#include #include "dialog/rendercancel/rendercancel.h" #include "decodercache.h" #include "node/graph.h" #include "node/output/viewer/viewer.h" -#include "renderworker.h" +#include "node/traverser.h" +#include "render/backend/colorprocessorcache.h" OLIVE_NAMESPACE_ENTER -class RenderBackend : public QObject +class RenderBackend : public QObject, public NodeTraverser { Q_OBJECT public: RenderBackend(QObject* parent = nullptr); - bool Init(); - - void Close(); - - const QString& GetError() const; - void SetViewerNode(ViewerOutput* viewer_node); - bool IsInitiated(); - - ViewerOutput* viewer_node() const; - void CancelQueue(); -public slots: - void InvalidateCache(const TimeRange &range, NodeInput *from); + QFuture Hash(const rational& time); -signals: - void QueueComplete(); + QFuture RenderFrame(const rational& time); + + void SetDivider(const int& divider); + + void SetMode(const RenderMode::Mode& mode); + + void SetPixelFormat(const PixelFormat::Format& pix_fmt); + + void SetSampleFormat(const SampleFormat::Format& sample_fmt); + +public slots: + void NodeGraphChanged(NodeInput *from, NodeInput *source); protected: - void RegenerateCacheID(); + virtual void TextureToFrame(const QVariant& texture, FramePtr frame) const = 0; - virtual bool InitInternal(); + virtual QVariant FrameToTexture(FramePtr frame) const = 0; - virtual void CloseInternal(); + virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) const override; - virtual bool CanRender(); - - virtual TimeRange PopNextFrameFromQueue(); - - rational GetSequenceLength(); - - const QVector& threads(); - - /** - * @brief Internal function for generating the cache ID - */ - virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) = 0; - - virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range); - - virtual void CacheIDChangedEvent(const QString& id); - - virtual void WorkerAboutToStartEvent(RenderWorker* worker); - - void SetError(const QString& error); - - virtual void ConnectViewer(ViewerOutput* node); - virtual void DisconnectViewer(ViewerOutput* node); - - /** - * @brief Function called when there are frames in the queue to cache - * - * This function is NOT thread-safe and should only be called in the main thread. - */ - void CacheNext(); - - void InitWorkers(); - - virtual NodeInput* GetDependentInput() = 0; - - virtual void ConnectWorkerToThis(RenderWorker* worker) = 0; - - bool ViewerIsConnected() const; - - const QString& cache_id() const; - - void QueueValueUpdate(NodeInput *from); - - bool AllProcessorsAreAvailable() const; - bool WorkerIsBusy(RenderWorker* worker) const; - void SetWorkerBusyState(RenderWorker* worker, bool busy); - - TimeRangeList cache_queue_; - - QVector processors_; - - QHash render_job_info_; - - QHash node_copy_map_; - - NodeGraph copied_graph_; + virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange &range) const override; private: - void CopyNodeInputValue(NodeInput* input); - Node *CopyNodeConnections(Node *src_node); - void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input); - - /** - * @brief Internal list of RenderProcessThreads - */ - QVector threads_; - - /** - * @brief Internal variable that contains whether the Renderer has started or not - */ - bool started_; + FramePtr RenderFrameInternal(const rational& time) const; /** * @brief Internal reference to attached viewer node */ ViewerOutput* viewer_node_; - /** - * @brief Internal reference to the copied viewer node we made in the compilation process - */ - ViewerOutput* copied_viewer_node_; - - /** - * @brief Error string that can be set in SetError() to handle failures - */ - QString error_; - - QString cache_id_; - - QList input_update_queued_; - - QVector processor_busy_state_; - RenderCancelDialog* cancel_dialog_; - struct FootageWaitInfo { + VideoRenderingParams video_params() const; + + class CopyMap { + public: + CopyMap(); + + void Init(ViewerOutput* viewer); + + void Queue(NodeInput* input); + + void ProcessQueue(); + + void Clear(); + + QThreadPool* thread_pool() { + return &thread_pool_; + } + + private: + void CopyNodeInputValue(NodeInput* input); + Node *CopyNodeConnections(Node *src_node); + void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input); + + ViewerOutput* original_viewer_; + ViewerOutput* copied_viewer_; + QList queued_updates_; + QHash copy_map_; + QThreadPool thread_pool_; + + }; + + // VIDEO MEMBERS + CopyMap video_copy_map_; + int divider_; + RenderMode::Mode render_mode_; + PixelFormat::Format pix_fmt_; + + ColorProcessorCache color_cache_; + + struct CachedStill { + NodeValue texture; + QString colorspace; + bool alpha_is_associated; + int divider; + rational time; + }; + + RenderCache still_image_cache_; + + // AUDIO MEMBERS + CopyMap audio_copy_map_; + SampleFormat::Format sample_fmt_; + + struct ConformWaitInfo { StreamPtr stream; TimeRange affected_range; rational stream_time; - bool operator==(const FootageWaitInfo& rhs) const; + bool operator==(const ConformWaitInfo& rhs) const; }; - QList footage_wait_info_; + QList footage_wait_info_; + +private slots: + void AudioCallback(); }; diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp deleted file mode 100644 index af23a08c3..000000000 --- a/app/render/backend/renderworker.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/*** - - 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 "renderworker.h" - -#include - -#include "node/block/block.h" - -OLIVE_NAMESPACE_ENTER - -RenderWorker::RenderWorker(QObject *parent) : - QObject(parent), - started_(false) -{ -} - -bool RenderWorker::Init() -{ - if (started_) { - return true; - } - - if (!(started_ = InitInternal())) { - Close(); - } - - return started_; -} - -void RenderWorker::Close() -{ - CloseInternal(); - - decoder_cache_.Clear(); - - started_ = false; -} - -void RenderWorker::Render(NodeDependency path, qint64 job_time) -{ - path_ = path; - - emit CompletedCache(path, RenderInternal(path, job_time), job_time); -} - -NodeValueTable RenderWorker::RenderInternal(const NodeDependency &path, const qint64 &job_time) -{ - Q_UNUSED(job_time) - - return ProcessNode(path); -} - -void RenderWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable& output_params) -{ - Q_UNUSED(node) - Q_UNUSED(range) - Q_UNUSED(input_params) - Q_UNUSED(output_params) -} - -DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream) -{ - // Access a map of Node inputs and decoder instances and retrieve a frame! - - DecoderPtr decoder = decoder_cache_.Get(stream.get()); - - if (!decoder && stream) { - // Create a new Decoder here - decoder = Decoder::CreateFromID(stream->footage()->decoder()); - decoder->set_stream(stream); - - if (decoder->Open()) { - decoder_cache_.Add(stream.get(), decoder); - } else { - decoder = nullptr; - qWarning() << "Failed to open decoder for" << stream->footage()->filename() << "::" << stream->index(); - } - } - - return decoder; -} - -NodeValue RenderWorker::GetDataFromStream(StreamPtr stream, const TimeRange &input_time) -{ - DecoderPtr decoder = ResolveDecoderFromInput(stream); - - if (decoder) { - return FrameToValue(decoder, stream, input_time); - } - - return NodeValue(); -} - -bool RenderWorker::IsStarted() -{ - return started_; -} - -void RenderWorker::ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) -{ - // Check if we have a shader for this output - RunNodeAccelerated(node, range, input_params, output_params); -} - -const NodeDependency &RenderWorker::CurrentPath() const -{ - return path_; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h deleted file mode 100644 index 0a811912f..000000000 --- a/app/render/backend/renderworker.h +++ /dev/null @@ -1,81 +0,0 @@ -/*** - - 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 RENDERWORKER_H -#define RENDERWORKER_H - -#include - -#include "decodercache.h" -#include "node/node.h" -#include "node/output/track/track.h" -#include "node/traverser.h" - -OLIVE_NAMESPACE_ENTER - -class RenderWorker : public QObject, public NodeTraverser -{ - Q_OBJECT -public: - RenderWorker(QObject* parent = nullptr); - - bool Init(); - - bool IsStarted(); - -public slots: - void Close(); - - void Render(OLIVE_NAMESPACE::NodeDependency path, qint64 job_time); - -signals: - void CompletedCache(OLIVE_NAMESPACE::NodeDependency dep, OLIVE_NAMESPACE::NodeValueTable data, qint64 job_time); - -protected: - virtual bool InitInternal() = 0; - - virtual void CloseInternal() = 0; - - virtual NodeValueTable RenderInternal(const NodeDependency& CurrentPath, const qint64& job_time); - - virtual void RunNodeAccelerated(const Node *node, const TimeRange& range, NodeValueDatabase &input_params, NodeValueTable &output_params); - - virtual NodeValue FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) = 0; - - virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) override; - - DecoderPtr ResolveDecoderFromInput(StreamPtr stream); - - NodeValue GetDataFromStream(StreamPtr stream, const TimeRange& input_time); - - const NodeDependency& CurrentPath() const; - -private: - bool started_; - - DecoderCache decoder_cache_; - - NodeDependency path_; - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // RENDERWORKER_H diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp deleted file mode 100644 index 492652e5d..000000000 --- a/app/render/backend/videorenderbackend.cpp +++ /dev/null @@ -1,354 +0,0 @@ -/*** - - 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 "videorenderbackend.h" - -#include -#include -#include -#include -#include -#include - -#include "common/timecodefunctions.h" -#include "config/config.h" -#include "render/diskmanager.h" -#include "render/diskmanager.h" -#include "render/pixelformat.h" -#include "videorenderworker.h" - -OLIVE_NAMESPACE_ENTER - -VideoRenderBackend::VideoRenderBackend(QObject *parent) : - RenderBackend(parent), - operating_mode_(VideoRenderWorker::kRenderOnly), - only_signal_last_frame_requested_(true), - limit_caching_(true) -{ - connect(DiskManager::instance(), &DiskManager::DeletedFrame, this, &VideoRenderBackend::FrameRemovedFromDiskCache); -} - -void VideoRenderBackend::ConnectViewer(ViewerOutput *node) -{ - connect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache); - connect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength); -} - -void VideoRenderBackend::DisconnectViewer(ViewerOutput *node) -{ - disconnect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache); - disconnect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength); - - frame_cache_.Clear(); -} - -const VideoRenderingParams &VideoRenderBackend::params() const -{ - return params_; -} - -void VideoRenderBackend::SetParameters(const VideoRenderingParams& params) -{ - CancelQueue(); - - // Set new parameters - params_ = params; - - // Handle custom events from derivatives - ParamsChangedEvent(); - - // Set params on all processors - foreach (RenderWorker* worker, processors_) { - static_cast(worker)->SetParameters(params_); - } - - // Regenerate the cache ID - RegenerateCacheID(); -} - -void VideoRenderBackend::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode) -{ - CancelQueue(); - - operating_mode_ = mode; - - foreach (RenderWorker* worker, processors_) { - static_cast(worker)->SetOperatingMode(operating_mode_); - } -} - -void VideoRenderBackend::SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix) -{ - foreach (RenderWorker* worker, processors_) { - static_cast(worker)->SetFrameGenerationParams(width, height, matrix); - } -} - -void VideoRenderBackend::SetOnlySignalLastFrameRequested(bool enabled) -{ - only_signal_last_frame_requested_ = enabled; -} - -bool VideoRenderBackend::IsRendered(const rational &time) const -{ - TimeRange range(time, time); - - return !TimeIsQueued(range) && !render_job_info_.contains(range); -} - -void VideoRenderBackend::SetLimitCaching(bool limit) -{ - limit_caching_ = limit; -} - -bool VideoRenderBackend::GenerateCacheIDInternal(QCryptographicHash& hash) -{ - if (!params_.is_valid()) { - return false; - } - - // Generate an ID that is more or less guaranteed to be unique to this Sequence - hash.addData(QString::number(params_.width()).toUtf8()); - hash.addData(QString::number(params_.height()).toUtf8()); - hash.addData(QString::number(params_.format()).toUtf8()); - hash.addData(QString::number(params_.divider()).toUtf8()); - - return true; -} - -void VideoRenderBackend::CacheIDChangedEvent(const QString &id) -{ - frame_cache_.SetCacheID(id); -} - -void VideoRenderBackend::ConnectWorkerToThis(RenderWorker *processor) -{ - VideoRenderWorker* video_processor = static_cast(processor); - - video_processor->SetOperatingMode(operating_mode_); - - connect(video_processor, &VideoRenderWorker::HashAlreadyBeingCached, this, &VideoRenderBackend::ThreadSkippedFrame, Qt::QueuedConnection); - connect(video_processor, &VideoRenderWorker::CompletedDownload, this, &VideoRenderBackend::ThreadCompletedDownload, Qt::QueuedConnection); - connect(video_processor, &VideoRenderWorker::HashAlreadyExists, this, &VideoRenderBackend::ThreadHashAlreadyExists, Qt::QueuedConnection); - connect(video_processor, &VideoRenderWorker::GeneratedFrame, this, &VideoRenderBackend::GeneratedFrame, Qt::QueuedConnection); - connect(video_processor, &VideoRenderWorker::GeneratedFrame, this, &VideoRenderBackend::ThreadGeneratedFrame, Qt::QueuedConnection); -} - -void VideoRenderBackend::InvalidateCacheInternal(const rational &start_range, const rational &end_range) -{ - TimeRange invalidated(start_range, end_range); - - if (limit_caching_) { - invalidated_.InsertTimeRange(invalidated); - } else { - RenderBackend::InvalidateCacheInternal(start_range, end_range); - } - - emit RangeInvalidated(invalidated); -} - -void VideoRenderBackend::RenderFrame(const rational &time) -{ - RenderBackend::InvalidateCacheInternal(time, time); -} - -VideoRenderFrameCache *VideoRenderBackend::frame_cache() -{ - return &frame_cache_; -} - -QString VideoRenderBackend::GetCachedFrame(const rational &time) -{ - UpdateLastRequestedTime(time); - - if (viewer_node() == nullptr) { - // Nothing is connected - nothing to show or render - return nullptr; - } - - if (cache_id().isEmpty()) { - qWarning() << "No cache ID"; - return nullptr; - } - - if (!params_.is_valid()) { - qWarning() << "Invalid parameters"; - return nullptr; - } - - // Find frame in map - QByteArray frame_hash = frame_cache_.TimeToHash(time); - - if (!frame_hash.isEmpty()) { - DiskManager::instance()->Accessed(frame_hash); - - return frame_cache_.CachePathName(frame_hash, params_.format()); - } - - return QString(); -} - -void VideoRenderBackend::UpdateLastRequestedTime(const rational &time) -{ - last_time_requested_ = time; -} - -NodeInput *VideoRenderBackend::GetDependentInput() -{ - return viewer_node()->texture_input(); -} - -bool VideoRenderBackend::CanRender() -{ - return params_.is_valid(); -} - -TimeRange VideoRenderBackend::PopNextFrameFromQueue() -{ - rational earliest_frame = RATIONAL_MAX; - - // Find earliest frame in the cache queue - foreach (const TimeRange& range, cache_queue_) { - if (range.in() < earliest_frame) { - earliest_frame = range.in(); - } - } - - // Snap this frame to the timebase - rational snapped_frame = Timecode::snap_time_to_timebase(earliest_frame, params_.time_base()); - rational next_frame; - - if (snapped_frame > earliest_frame) { - next_frame = snapped_frame; - snapped_frame -= params_.time_base(); - } else { - next_frame = snapped_frame + params_.time_base(); - } - - TimeRange frame_range(snapped_frame, next_frame); - - // Remove this particular frame from the queue - cache_queue_.RemoveTimeRange(frame_range); - - // Remove this particular frame from missing frames - invalidated_.RemoveTimeRange(frame_range); - - // Return the snapped frame - return TimeRange(frame_range.in(), frame_range.in()); -} - -void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash) -{ - SetWorkerBusyState(static_cast(sender()), false); - - SetFrameHash(dep, hash, job_time); - - QList hashes_with_time = frame_cache()->FramesWithHash(hash); - - foreach (const rational& t, hashes_with_time) { - emit CachedTimeReady(t, job_time); - } - - // Queue up a new frame for this worker - CacheNext(); -} - -void VideoRenderBackend::ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash) -{ - SetWorkerBusyState(static_cast(sender()), false); - - if (SetFrameHash(dep, hash, job_time) - && frame_cache_.HasHash(hash, params_.format())) { - emit CachedTimeReady(dep.in(), job_time); - } - - // Queue up a new frame for this worker - CacheNext(); -} - -void VideoRenderBackend::ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash) -{ - SetWorkerBusyState(static_cast(sender()), false); - - if (SetFrameHash(dep, hash, job_time)) { - emit CachedTimeReady(dep.in(), job_time); - } - - // Queue up a new frame for this worker - CacheNext(); -} - -void VideoRenderBackend::ThreadGeneratedFrame() -{ - SetWorkerBusyState(static_cast(sender()), false); - - CacheNext(); -} - -void VideoRenderBackend::TruncateFrameCacheLength(const rational &length) -{ - // Remove frames after this time code if it's changed - frame_cache_.Truncate(length); - - invalidated_.RemoveTimeRange(TimeRange(length, RATIONAL_MAX)); - - // If the playhead is past the length, update the viewer to a null texture because it won't be cached through the - // queue, but will now be a null texture - if (last_time_requested_ >= length) { - emit CachedTimeReady(last_time_requested_, QDateTime::currentMSecsSinceEpoch()); - } -} - -void VideoRenderBackend::FrameRemovedFromDiskCache(const QByteArray &hash) -{ - QList deleted_frames = frame_cache()->TakeFramesWithHash(hash); - - foreach (const rational& frame, deleted_frames) { - TimeRange invalidated(frame, frame+params_.time_base()); - - invalidated_.InsertTimeRange(invalidated); - - emit RangeInvalidated(invalidated); - } -} - -bool VideoRenderBackend::TimeIsQueued(const TimeRange &time) const -{ - return cache_queue_.ContainsTimeRange(time, true, false); -} - -bool VideoRenderBackend::JobIsCurrent(const NodeDependency &dep, const qint64& job_time) const -{ - return (render_job_info_.value(dep.range()) == job_time && !TimeIsQueued(dep.range())); -} - -bool VideoRenderBackend::SetFrameHash(const NodeDependency &dep, const QByteArray &hash, const qint64& job_time) -{ - if (JobIsCurrent(dep, job_time)) { - frame_cache_.SetHash(dep.in(), hash); - render_job_info_.remove(dep.range()); - - return true; - } - - return false; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/videorenderbackend.h b/app/render/backend/videorenderbackend.h deleted file mode 100644 index 486cfebdb..000000000 --- a/app/render/backend/videorenderbackend.h +++ /dev/null @@ -1,150 +0,0 @@ -/*** - - 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 VIDEORENDERERBACKEND_H -#define VIDEORENDERERBACKEND_H - -#include - -#include "colorprocessorcache.h" -#include "node/output/viewer/viewer.h" -#include "renderbackend.h" -#include "render/pixelformat.h" -#include "render/rendermodes.h" -#include "videorenderframecache.h" -#include "videorenderworker.h" - -OLIVE_NAMESPACE_ENTER - -/** - * @brief A multithreaded OpenGL based renderer for node systems - */ -class VideoRenderBackend : public RenderBackend -{ - Q_OBJECT -public: - /** - * @brief Renderer Constructor - * - * Constructing a Renderer object will not start any threads/backend on its own. Use Start() to do this and Stop() - * when the Renderer is about to be destroyed. - */ - VideoRenderBackend(QObject* parent = nullptr); - - /** - * @brief Set parameters of the Renderer - * - * The Renderer owns the buffers that are used in the rendering process and this function sets the kind of buffers - * to use. The Renderer must be stopped when calling this function. - */ - void SetParameters(const VideoRenderingParams ¶ms); - - void SetOperatingMode(const VideoRenderWorker::OperatingMode& mode); - - void SetFrameGenerationParams(int width, int height, const QMatrix4x4& matrix); - - void SetOnlySignalLastFrameRequested(bool enabled); - - bool IsRendered(const rational& time) const; - - void SetLimitCaching(bool limit); - - QString GetCachedFrame(const rational& time); - - void UpdateLastRequestedTime(const rational& time); - - VideoRenderFrameCache* frame_cache(); - - const VideoRenderingParams& params() const; - - void RenderFrame(const rational& time); - -protected: - struct HashTimeMapping { - rational time; - QByteArray hash; - }; - - virtual void ConnectViewer(ViewerOutput* node) override; - - virtual void DisconnectViewer(ViewerOutput* node) override; - - virtual NodeInput* GetDependentInput() override; - - virtual bool CanRender() override; - - virtual TimeRange PopNextFrameFromQueue() override; - - /** - * @brief Internal function for generating the cache ID - */ - virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) override; - - virtual void CacheIDChangedEvent(const QString& id) override; - - virtual void ConnectWorkerToThis(RenderWorker* processor) override; - - virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range) override; - - virtual void ParamsChangedEvent(){} - - VideoRenderWorker::OperatingMode operating_mode_; - -signals: - void CachedTimeReady(const rational& time, qint64 job_time); - - void RangeInvalidated(const TimeRange& range); - - void GeneratedFrame(FramePtr frame); - -private: - bool TimeIsQueued(const TimeRange &time) const; - - bool JobIsCurrent(const NodeDependency &dep, const qint64& job_time) const; - - bool SetFrameHash(const NodeDependency& dep, const QByteArray& hash, const qint64& job_time); - - VideoRenderingParams params_; - - VideoRenderFrameCache frame_cache_; - - TimeRangeList invalidated_; - - rational last_time_requested_; - - bool only_signal_last_frame_requested_; - - bool limit_caching_; - -private slots: - void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash); - void ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash); - void ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash); - void ThreadGeneratedFrame(); - - void TruncateFrameCacheLength(const rational& length); - - void FrameRemovedFromDiskCache(const QByteArray& hash); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // VIDEORENDERERBACKEND_H diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp deleted file mode 100644 index f1e3c508b..000000000 --- a/app/render/backend/videorenderworker.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/*** - - 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 "videorenderworker.h" - -#include "common/define.h" -#include "common/functiontimer.h" -#include "node/block/transition/transition.h" -#include "node/node.h" -#include "project/project.h" -#include "render/pixelformat.h" - -OLIVE_NAMESPACE_ENTER - -VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, QObject *parent) : - RenderWorker(parent), - frame_cache_(frame_cache), - operating_mode_(kHashRenderCache) -{ -} - -const VideoRenderingParams &VideoRenderWorker::video_params() -{ - return video_params_; -} - -void VideoRenderWorker::TextureToBuffer(const QVariant &texture, void *buffer, int linesize) -{ - TextureToBuffer(texture, - video_params_.effective_width(), - video_params_.effective_height(), - QMatrix4x4(), - buffer, - linesize); -} - -NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, const qint64 &job_time) -{ - // Get hash of node graph - // We use SHA-1 for speed (benchmarks show it's the fastest hash available to us) - QByteArray hash; - if (operating_mode_ & kHashOnly) { - QCryptographicHash hasher(QCryptographicHash::Sha1); - - // Embed video parameters into this hash - int vwidth = video_params_.effective_width(); - int vheight = video_params_.effective_height(); - PixelFormat::Format vfmt = video_params_.format(); - RenderMode::Mode vmode = video_params_.mode(); - - hasher.addData(reinterpret_cast(&vwidth), sizeof(int)); - hasher.addData(reinterpret_cast(&vheight), sizeof(int)); - hasher.addData(reinterpret_cast(&vfmt), sizeof(PixelFormat::Format)); - hasher.addData(reinterpret_cast(&vmode), sizeof(RenderMode::Mode)); - - path.node()->Hash(hasher, path.in()); - hash = hasher.result(); - } - - NodeValueTable value; - - if (!(operating_mode_ & kRenderOnly)) { - - // Emit only the hash - emit CompletedDownload(path, job_time, hash); - - } else if ((operating_mode_ & kHashOnly) && frame_cache_->HasHash(hash, video_params_.format())) { - - // We've already cached this hash, no need to continue - emit HashAlreadyExists(path, job_time, hash); - - } else if (!(operating_mode_ & kHashOnly) || frame_cache_->TryCache(hash)) { - - // This hash is available for us to cache, start traversing graph - value = ProcessNode(path); - - // Find texture in hash - QVariant texture = value.Get(NodeParam::kTexture); - - // If we actually have a texture, download it into the disk cache - if (!texture.isNull() || (!(operating_mode_ & kDownloadOnly))) { - Download(hash, path.in(), texture); - } - - frame_cache_->RemoveHashFromCurrentlyCaching(hash); - - // Signal that this job is complete - if (operating_mode_ & kDownloadOnly) { - emit CompletedDownload(path, job_time, hash); - } - - } else { - - // Another thread must be caching this already, nothing to be done - emit HashAlreadyBeingCached(path, job_time, hash); - - } - - return value; -} - -void VideoRenderWorker::FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable *table) -{ - if (stream->type() != Stream::kVideo && stream->type() != Stream::kImage) { - return; - } - - ImageStreamPtr video_stream = std::static_pointer_cast(stream); - rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time.in(); - QString colorspace_match = video_stream->get_colorspace_match_string(); - - NodeValue value; - bool found_cache = false; - - if (still_image_cache_.Has(stream.get())) { - CachedStill cs = still_image_cache_.Get(stream.get()); - - if (cs.colorspace == colorspace_match - && cs.alpha_is_associated == video_stream->premultiplied_alpha() - && cs.divider == video_params_.divider() - && cs.time == time_match) { - value = cs.texture; - found_cache = true; - } else { - still_image_cache_.Remove(stream.get()); - } - } - - if (!found_cache) { - - value = GetDataFromStream(stream, input_time); - - still_image_cache_.Add(stream.get(), {value, - colorspace_match, - video_stream->premultiplied_alpha(), - video_params_.divider(), - time_match}); - - } - - table->Push(value); -} - -void VideoRenderWorker::SetParameters(const VideoRenderingParams &video_params) -{ - video_params_ = video_params; - - if (IsStarted()) { - ResizeDownloadBuffer(); - } - - ParametersChangedEvent(); -} - -void VideoRenderWorker::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode) -{ - operating_mode_ = mode; -} - -void VideoRenderWorker::SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix) -{ - frame_gen_params_ = VideoRenderingParams(width, - height, - video_params_.time_base(), - video_params_.format(), - video_params_.mode(), - video_params_.divider()); - frame_gen_mat_ = matrix; -} - -bool VideoRenderWorker::InitInternal() -{ - if (video_params_.is_valid()) { - ResizeDownloadBuffer(); - } - - return true; -} - -void VideoRenderWorker::CloseInternal() -{ - download_buffer_.clear(); -} - -void VideoRenderWorker::Download(const QByteArray& hash, const rational& time, QVariant texture) -{ - if (operating_mode_ & kDownloadOnly) { - - TextureToBuffer(texture, download_buffer_.data(), 0); - - frame_cache_->SaveCacheFrame(hash, - download_buffer_.data(), - VideoRenderingParams(video_params_.effective_width(), - video_params_.effective_height(), - video_params_.format())); - - } else { - - FramePtr frame = Frame::Create(); - - if (frame_gen_params_.is_valid()) { - frame->set_video_params(frame_gen_params_); - } else { - frame->set_video_params(VideoRenderingParams(video_params_.effective_width(), - video_params_.effective_height(), - video_params_.format())); - } - - frame->allocate(); - - if (texture.isNull()) { - memset(frame->data(), 0, frame->allocated_size()); - } else { - TextureToBuffer(texture, frame->width(), frame->height(), frame_gen_mat_, frame->data(), frame->linesize_pixels()); - } - - frame->set_timestamp(time); - - emit GeneratedFrame(frame); - - } -} - -void VideoRenderWorker::ResizeDownloadBuffer() -{ - download_buffer_.resize(PixelFormat::GetBufferSize(video_params_.format(), video_params_.effective_width(), video_params_.effective_height())); -} - -ColorProcessorCache *VideoRenderWorker::color_cache() -{ - return &color_cache_; -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h deleted file mode 100644 index e94c1a4c9..000000000 --- a/app/render/backend/videorenderworker.h +++ /dev/null @@ -1,140 +0,0 @@ -/*** - - 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 VIDEORENDERWORKER_H -#define VIDEORENDERWORKER_H - -#include -#include - -#include "colorprocessorcache.h" -#include "node/dependency.h" -#include "render/videoparams.h" -#include "renderworker.h" -#include "videorenderframecache.h" - -OLIVE_NAMESPACE_ENTER - -class VideoRenderWorker : public RenderWorker { - Q_OBJECT -public: - /** - * @brief VideoRenderWorker uses hashes to recognize frames that are identical to others in the render queue - * - * This mode can modify the behavior of the worker, either to disable hash verification or only generate hashes and - * not render. These are only used in the context of exporting. - * - * These are also flags that can be or'd together, mostly for the convenience of checking which functionalities are - * disabled and enabled. - */ - enum OperatingMode { - /// Generate hashes but don't render or download anything - kHashOnly = 0x1, - - /// Render but don't download or hash - kRenderOnly = 0x2, - - /// Enable download (NEVER USE THIS ON ITS OWN, this is only here for checking flags, download-only mode makes no sense) - kDownloadOnly = 0x4, - - /// Use hash verification and render, but don't cache any frames to disk - kHashAndRenderOnly = 0x3, - - /// Render and download, but don't use hash verification - kRenderAndCacheOnly = 0x6, - - /// Render and use hashes to identify exact matches (default) - kHashRenderCache = 0x7 - }; - - VideoRenderWorker(VideoRenderFrameCache* frame_cache, QObject* parent = nullptr); - - void SetParameters(const VideoRenderingParams& video_params); - - void SetOperatingMode(const OperatingMode& mode); - - void SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix); - -signals: - void CompletedDownload(NodeDependency path, qint64 job_time, QByteArray hash); - - void HashAlreadyBeingCached(NodeDependency path, qint64 job_time, QByteArray hash); - - void HashAlreadyExists(NodeDependency path, qint64 job_time, QByteArray hash); - - void GeneratedFrame(FramePtr frame); - - void Aborted(); - -protected: - virtual bool InitInternal() override; - - virtual void CloseInternal() override; - - const VideoRenderingParams& video_params(); - - virtual void ParametersChangedEvent(){} - - void TextureToBuffer(const QVariant& texture, void *buffer, int linesize); - - virtual void TextureToBuffer(const QVariant& texture, int width, int height, const QMatrix4x4& matrix, void *buffer, int linesize) = 0; - - virtual NodeValueTable RenderInternal(const NodeDependency& CurrentPath, const qint64& job_time) override; - - virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) override; - - ColorProcessorCache* color_cache(); - -private: - void Download(const QByteArray &hash, const rational &time, QVariant texture); - - void ResizeDownloadBuffer(); - - VideoRenderingParams frame_gen_params_; - - QMatrix4x4 frame_gen_mat_; - - VideoRenderingParams video_params_; - - VideoRenderFrameCache* frame_cache_; - - ColorProcessorCache color_cache_; - - QByteArray download_buffer_; - - OperatingMode operating_mode_; - - struct CachedStill { - NodeValue texture; - QString colorspace; - bool alpha_is_associated; - int divider; - rational time; - }; - - RenderCache still_image_cache_; - -private slots: - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // VIDEORENDERWORKER_H diff --git a/app/render/backend/videorenderframecache.cpp b/app/render/framehashcache.cpp similarity index 73% rename from app/render/backend/videorenderframecache.cpp rename to app/render/framehashcache.cpp index 6c54a06b2..070a53094 100644 --- a/app/render/backend/videorenderframecache.cpp +++ b/app/render/framehashcache.cpp @@ -18,7 +18,7 @@ ***/ -#include "videorenderframecache.h" +#include "framehashcache.h" #include #include @@ -33,80 +33,24 @@ OLIVE_NAMESPACE_ENTER -void VideoRenderFrameCache::Clear() -{ - time_hash_map_.clear(); - - { - QMutexLocker locker(¤tly_caching_lock_); - currently_caching_list_.clear(); - } - - cache_id_.clear(); -} - -bool VideoRenderFrameCache::HasHash(const QByteArray &hash, const PixelFormat::Format& format) -{ - return QFileInfo::exists(CachePathName(hash, format)) && !IsCaching(hash); -} - -bool VideoRenderFrameCache::IsCaching(const QByteArray &hash) -{ - QMutexLocker locker(¤tly_caching_lock_); - - return currently_caching_list_.contains(hash); -} - -bool VideoRenderFrameCache::TryCache(const QByteArray &hash) -{ - QMutexLocker locker(¤tly_caching_lock_); - - if (!currently_caching_list_.contains(hash)) { - currently_caching_list_.append(hash); - return true; - } - - return false; -} - -void VideoRenderFrameCache::SetCacheID(const QString &id) -{ - Clear(); - - cache_id_ = id; -} - -QByteArray VideoRenderFrameCache::TimeToHash(const rational &time) const +QByteArray FrameHashCache::GetHash(const rational &time) const { return time_hash_map_.value(time); } -void VideoRenderFrameCache::SetHash(const rational &time, const QByteArray &hash) +void FrameHashCache::SetHash(const rational &time, const QByteArray &hash) { time_hash_map_.insert(time, hash); + + Validate(TimeRange(time, time + timebase_)); } -void VideoRenderFrameCache::Truncate(const rational &time) +void FrameHashCache::SetTimebase(const rational &tb) { - QMap::iterator i = time_hash_map_.begin(); - - while (i != time_hash_map_.end()) { - if (i.key() >= time) { - i = time_hash_map_.erase(i); - } else { - i++; - } - } + timebase_ = tb; } -void VideoRenderFrameCache::RemoveHashFromCurrentlyCaching(const QByteArray &hash) -{ - QMutexLocker locker(¤tly_caching_lock_); - - currently_caching_list_.removeOne(hash); -} - -QList VideoRenderFrameCache::FramesWithHash(const QByteArray &hash) const +QList FrameHashCache::GetFramesWithHash(const QByteArray &hash) const { QList times; @@ -121,7 +65,7 @@ QList VideoRenderFrameCache::FramesWithHash(const QByteArray &hash) co return times; } -QList VideoRenderFrameCache::TakeFramesWithHash(const QByteArray &hash) +QList FrameHashCache::TakeFramesWithHash(const QByteArray &hash) { QList times; @@ -140,12 +84,12 @@ QList VideoRenderFrameCache::TakeFramesWithHash(const QByteArray &hash return times; } -const QMap &VideoRenderFrameCache::time_hash_map() const +const QMap &FrameHashCache::time_hash_map() const { return time_hash_map_; } -QString VideoRenderFrameCache::GetFormatExtension(const PixelFormat::Format &f) +QString FrameHashCache::GetFormatExtension(const PixelFormat::Format &f) { if (PixelFormat::FormatIsFloat(f)) { // EXR is only fast with float buffers so we only use it for those @@ -160,9 +104,9 @@ QString VideoRenderFrameCache::GetFormatExtension(const PixelFormat::Format &f) } } -void VideoRenderFrameCache::SaveCacheFrame(const QByteArray& hash, - char* data, - const VideoRenderingParams& vparam) const +void FrameHashCache::SaveCacheFrame(const QByteArray& hash, + char* data, + const VideoRenderingParams& vparam) { QString fn = CachePathName(hash, vparam.format()); @@ -172,7 +116,35 @@ void VideoRenderFrameCache::SaveCacheFrame(const QByteArray& hash, } } -QString VideoRenderFrameCache::CachePathName(const QByteArray& hash, const PixelFormat::Format& pix_fmt) const +void FrameHashCache::LengthChangedEvent(const rational &old, const rational &newlen) +{ + if (newlen < old) { + QMap::iterator i = time_hash_map_.begin(); + + while (i != time_hash_map_.end()) { + if (i.key() >= newlen) { + i = time_hash_map_.erase(i); + } else { + i++; + } + } + } +} + +void FrameHashCache::InvalidateEvent(const TimeRange &r) +{ + QMap::iterator i = time_hash_map_.begin(); + + while (i != time_hash_map_.end()) { + if (i.key() >= r.in() && i.key() < r.out()) { + i = time_hash_map_.erase(i); + } else { + i++; + } + } +} + +QString FrameHashCache::CachePathName(const QByteArray& hash, const PixelFormat::Format& pix_fmt) { QString ext = GetFormatExtension(pix_fmt); @@ -184,7 +156,7 @@ QString VideoRenderFrameCache::CachePathName(const QByteArray& hash, const Pixel return cache_dir.filePath(filename); } -bool VideoRenderFrameCache::SaveCacheFrame(const QString &filename, char *data, const VideoRenderingParams &vparam) +bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoRenderingParams &vparam) { switch (vparam.format()) { case PixelFormat::PIX_FMT_RGB8: diff --git a/app/render/backend/videorenderframecache.h b/app/render/framehashcache.h similarity index 61% rename from app/render/backend/videorenderframecache.h rename to app/render/framehashcache.h index 5c326e3f1..343cfed0c 100644 --- a/app/render/backend/videorenderframecache.h +++ b/app/render/framehashcache.h @@ -24,47 +24,29 @@ #include #include "common/rational.h" +#include "common/timerange.h" #include "render/pixelformat.h" +#include "render/playbackcache.h" #include "render/videoparams.h" OLIVE_NAMESPACE_ENTER -class VideoRenderFrameCache +class FrameHashCache : public PlaybackCache { + Q_OBJECT public: - VideoRenderFrameCache() = default; + FrameHashCache() = default; - void Clear(); - - /** - * @brief Return whether a frame with this hash already exists - */ - bool HasHash(const QByteArray& hash, const PixelFormat::Format &format); - - /** - * @brief Return whether a frame is currently being cached - */ - bool IsCaching(const QByteArray& hash); - - /** - * @brief Check if a frame is currently being cached, and if not reserve it - */ - bool TryCache(const QByteArray& hash); - - void SetCacheID(const QString& id); - - QByteArray TimeToHash(const rational& time) const; + QByteArray GetHash(const rational& time) const; void SetHash(const rational& time, const QByteArray& hash); - void Truncate(const rational& time); - - void RemoveHashFromCurrentlyCaching(const QByteArray& hash); + void SetTimebase(const rational& tb); /** * @brief Returns a list of frames that use a particular hash */ - QList FramesWithHash(const QByteArray& hash) const; + QList GetFramesWithHash(const QByteArray& hash) const; /** * @brief Same as FramesWithHash() but also removes these frames from the map @@ -73,23 +55,26 @@ public: const QMap& time_hash_map() const; - static QString GetFormatExtension(const PixelFormat::Format& f); - /** * @brief Return the path of the cached image at this time */ - QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt) const; + static QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt); static bool SaveCacheFrame(const QString& filename, char *data, const VideoRenderingParams &vparam); - void SaveCacheFrame(const QByteArray& hash, char *data, const VideoRenderingParams &vparam) const; + static void SaveCacheFrame(const QByteArray& hash, char *data, const VideoRenderingParams &vparam); + + static QString GetFormatExtension(const PixelFormat::Format& f); + +protected: + virtual void LengthChangedEvent(const rational& old, const rational& newlen) override; + + virtual void InvalidateEvent(const TimeRange& range) override; private: QMap time_hash_map_; - QMutex currently_caching_lock_; - QVector currently_caching_list_; + rational timebase_; - QString cache_id_; }; OLIVE_NAMESPACE_EXIT diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp new file mode 100644 index 000000000..f5b39cee5 --- /dev/null +++ b/app/render/playbackcache.cpp @@ -0,0 +1,84 @@ +/*** + + 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 "playbackcache.h" + +OLIVE_NAMESPACE_ENTER + +PlaybackCache::PlaybackCache() +{ + +} + +void PlaybackCache::Invalidate(const TimeRange &r) +{ + invalidated_.InsertTimeRange(r); + + InvalidateEvent(r); + + emit Invalidated(r); +} + +void PlaybackCache::SetLength(const rational &r) +{ + if (length_ == r) { + // Same length - do nothing + return; + } + + if (r > length_) { + // If new length is greater, simply extend the invalidated range for now + invalidated_.InsertTimeRange(TimeRange(r, length_)); + } else { + // If new length is smaller, removed hashes + invalidated_.RemoveTimeRange(TimeRange(length_, r)); + } + + LengthChangedEvent(length_, r); + + length_ = r; +} + +bool PlaybackCache::IsFullyValidated() const +{ + return invalidated_.isEmpty(); +} + +void PlaybackCache::Validate(const TimeRange &r) +{ + invalidated_.RemoveTimeRange(r); + + emit Validated(r); +} + +void PlaybackCache::InvalidateAll() +{ + Invalidate(TimeRange(0, length_)); +} + +void PlaybackCache::LengthChangedEvent(const rational &, const rational &) +{ +} + +void PlaybackCache::InvalidateEvent(const TimeRange &) +{ +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/audio/audioworker.h b/app/render/playbackcache.h similarity index 50% rename from app/render/backend/audio/audioworker.h rename to app/render/playbackcache.h index fa642b11d..5a1040432 100644 --- a/app/render/backend/audio/audioworker.h +++ b/app/render/playbackcache.h @@ -18,27 +18,55 @@ ***/ -#ifndef AUDIOWORKER_H -#define AUDIOWORKER_H +#ifndef PLAYBACKCACHE_H +#define PLAYBACKCACHE_H -#include "../audiorenderworker.h" +#include + +#include "common/timerange.h" OLIVE_NAMESPACE_ENTER -class AudioWorker : public AudioRenderWorker +class PlaybackCache : public QObject { + Q_OBJECT public: - AudioWorker(QHash* copy_map, QObject* parent = nullptr); + PlaybackCache(); + + void Invalidate(const TimeRange& r); + + void SetLength(const rational& r); + + bool IsFullyValidated() const; + + const TimeRangeList& GetInvalidatedRanges() const + { + return invalidated_; + } + +signals: + void Invalidated(const TimeRange& r); + + void Validated(const TimeRange& r); + + void LengthChanged(const rational& r); protected: - virtual NodeValue FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) override; + void Validate(const TimeRange& r); - virtual void RunNodeAccelerated(const Node *node, const TimeRange& range, NodeValueDatabase& input_params, NodeValueTable& output_params) override; + void InvalidateAll(); + + virtual void LengthChangedEvent(const rational& old, const rational& newlen); + + virtual void InvalidateEvent(const TimeRange& range); private: + TimeRangeList invalidated_; + + rational length_; }; OLIVE_NAMESPACE_EXIT -#endif // AUDIOWORKER_H +#endif // PLAYBACKCACHE_H diff --git a/app/widget/timebased/timebased.cpp b/app/widget/timebased/timebased.cpp index e0afa2647..bfbd1ae98 100644 --- a/app/widget/timebased/timebased.cpp +++ b/app/widget/timebased/timebased.cpp @@ -103,7 +103,7 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) void TimeBasedWidget::UpdateMaximumScroll() { - rational length = (viewer_node_) ? viewer_node_->Length() : rational(); + rational length = (viewer_node_) ? viewer_node_->GetLength() : rational(); if (auto_max_scrollbar_) { scrollbar_->setMaximum(qMax(0, qCeil(TimeToScene(length)) - width())); @@ -244,7 +244,7 @@ void TimeBasedWidget::GoToPrevCut() int64_t closest_cut = 0; - foreach (TrackOutput* track, viewer_node_->Tracks()) { + foreach (TrackOutput* track, viewer_node_->GetTracks()) { int64_t this_track_closest_cut = 0; foreach (Block* block, track->Blocks()) { @@ -271,7 +271,7 @@ void TimeBasedWidget::GoToNextCut() int64_t closest_cut = INT64_MAX; - foreach (TrackOutput* track, GetConnectedNode()->Tracks()) { + foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) { int64_t this_track_closest_cut = Timecode::time_to_timestamp(track->track_length(), timebase()); if (this_track_closest_cut <= GetTimestamp()) { @@ -319,7 +319,7 @@ void TimeBasedWidget::NextFrame() void TimeBasedWidget::GoToEnd() { if (viewer_node_) { - SetTimeAndSignal(Timecode::time_to_timestamp(viewer_node_->Length(), timebase())); + SetTimeAndSignal(Timecode::time_to_timestamp(viewer_node_->GetLength(), timebase())); } } @@ -462,7 +462,7 @@ void TimeBasedWidget::ToggleShowAll() w = w / 10 * 9; toggle_show_all_old_scale_ = GetScale(); - SetScale(w / GetConnectedNode()->Length().toDouble()); + SetScale(w / GetConnectedNode()->GetLength().toDouble()); toggle_show_all_ = true; } } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index f2689d1e5..fe9af72e5 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -425,7 +425,7 @@ void TimelineWidget::SplitAtPlayhead() bool some_blocks_are_selected = false; // Get all blocks at the playhead - foreach (TrackOutput* track, GetConnectedNode()->Tracks()) { + foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) { Block* b = track->BlockContainingTime(playhead_time); if (b && b->type() == Block::kClip) { @@ -566,7 +566,7 @@ void TimelineWidget::IncreaseTrackHeight() return; } - QVector all_tracks = GetConnectedNode()->Tracks(); + QVector all_tracks = GetConnectedNode()->GetTracks(); // Increase the height of each track by one "unit" foreach (TrackOutput* t, all_tracks) { @@ -580,7 +580,7 @@ void TimelineWidget::DecreaseTrackHeight() return; } - QVector all_tracks = GetConnectedNode()->Tracks(); + QVector all_tracks = GetConnectedNode()->GetTracks(); // Decrease the height of each track by one "unit" foreach (TrackOutput* t, all_tracks) { @@ -714,7 +714,7 @@ void TimelineWidget::DeleteInToOut(bool ripple) QUndoCommand* command = new QUndoCommand(); - foreach (TrackOutput* track, GetConnectedNode()->Tracks()) { + foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) { if (!track->IsLocked()) { if (ripple) { new TrackRippleRemoveAreaCommand(track, @@ -794,7 +794,7 @@ void TimelineWidget::RippleEditTo(Timeline::MovementMode mode, bool insert_gaps) closest_point_to_playhead = RATIONAL_MAX; } - foreach (TrackOutput* track, GetConnectedNode()->Tracks()) { + foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) { Block* b = track->NearestBlockBefore(playhead_time); if (b != nullptr) { @@ -821,7 +821,7 @@ void TimelineWidget::RippleEditTo(Timeline::MovementMode mode, bool insert_gaps) rational out_ripple = qMax(closest_point_to_playhead, playhead_time); rational ripple_length = out_ripple - in_ripple; - foreach (TrackOutput* track, GetConnectedNode()->Tracks()) { + foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) { GapBlock* gap = nullptr; if (insert_gaps) { gap = new GapBlock(); @@ -854,7 +854,7 @@ void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational QList blocks_to_append_gap_to; QList gaps_to_extend; - foreach (TrackOutput* track, GetConnectedNode()->Tracks()) { + foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) { if (track->IsLocked()) { continue; } diff --git a/app/widget/timelinewidget/tool/ripple.cpp b/app/widget/timelinewidget/tool/ripple.cpp index a9a3d099c..d6c3ff800 100644 --- a/app/widget/timelinewidget/tool/ripple.cpp +++ b/app/widget/timelinewidget/tool/ripple.cpp @@ -118,7 +118,7 @@ void TimelineWidget::RippleTool::InitiateGhosts(TimelineViewBlockItem *clicked_i } // For each track that does NOT have a ghost, we need to make one for Gaps - foreach (TrackOutput* track, parent()->GetConnectedNode()->Tracks()) { + foreach (TrackOutput* track, parent()->GetConnectedNode()->GetTracks()) { // Determine if we've already created a ghost on this track bool ghost_on_this_track_exists = false; diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index 6833b6c67..6a2939466 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -727,7 +727,7 @@ void TimelineRippleDeleteGapsAtRegionsCommand::redo_internal() QList blocks_around_range; - foreach (TrackOutput* track, timeline_->Tracks()) { + foreach (TrackOutput* track, timeline_->GetTracks()) { // Get the block from every other track that is either at or just before our block's in point Block* block_at_time = track->NearestBlockBeforeOrAt(range.in()); diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index e47deeb5d..9818f00da 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -31,28 +31,28 @@ OLIVE_NAMESPACE_ENTER AudioWaveformView::AudioWaveformView(QWidget *parent) : SeekableWidget(parent), - backend_(nullptr) + playback_(nullptr) { setAutoFillBackground(true); setBackgroundRole(QPalette::Base); } -void AudioWaveformView::SetBackend(AudioRenderBackend *backend) +void AudioWaveformView::SetViewer(AudioPlaybackCache *playback) { - if (backend_) { - disconnect(backend_, &AudioRenderBackend::QueueComplete, this, &AudioWaveformView::ForceUpdate); - disconnect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged); + if (playback_) { + disconnect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::ForceUpdate); + disconnect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::BackendParamsChanged); SetTimebase(0); } - backend_ = backend; + playback_ = playback; - if (backend_) { - connect(backend_, &AudioRenderBackend::QueueComplete, this, &AudioWaveformView::ForceUpdate); - connect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged); + if (playback_) { + connect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::ForceUpdate); + connect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::BackendParamsChanged); - SetTimebase(backend_->params().time_base()); + SetTimebase(playback_->GetParameters().time_base()); } ForceUpdate(); @@ -113,12 +113,14 @@ void AudioWaveformView::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); - if (!backend_ || backend_->CachePathName().isEmpty() || !backend_->params().is_valid()) { + const AudioRenderingParams& params = playback_->GetParameters(); + + if (!playback_ + || playback_->GetCacheFilename().isEmpty() + || !params.is_valid()) { return; } - const AudioRenderingParams& params = backend_->params(); - if (cached_size_ != size() || cached_scale_ != GetScale() || cached_scroll_ != GetScroll()) { @@ -126,7 +128,7 @@ void AudioWaveformView::paintEvent(QPaintEvent *event) cached_waveform_ = QPixmap(size()); cached_waveform_.fill(Qt::transparent); - QFile fs(backend_->CachePathName()); + QFile fs(playback_->GetCacheFilename()); if (fs.open(QFile::ReadOnly)) { @@ -207,12 +209,12 @@ void AudioWaveformView::paintEvent(QPaintEvent *event) void AudioWaveformView::BackendParamsChanged() { - SetTimebase(backend_->params().time_base()); + SetTimebase(playback_->GetParameters().time_base()); } void AudioWaveformView::ForceUpdate() { - cached_size_ = QSize(); + cached_size_ = QSize(); update(); } diff --git a/app/widget/viewer/audiowaveformview.h b/app/widget/viewer/audiowaveformview.h index 0daebf879..e43999817 100644 --- a/app/widget/viewer/audiowaveformview.h +++ b/app/widget/viewer/audiowaveformview.h @@ -25,7 +25,7 @@ #include "audio/sumsamples.h" #include "render/audioparams.h" -#include "render/backend/audiorenderbackend.h" +#include "render/audioplaybackcache.h" #include "widget/timeruler/seekablewidget.h" OLIVE_NAMESPACE_ENTER @@ -38,7 +38,7 @@ public: //void SetData(const QString& file, const AudioRenderingParams& params); - void SetBackend(AudioRenderBackend* backend); + void SetViewer(AudioPlaybackCache *playback); static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const SampleSummer::Sum *samples, int nb_samples, int channels); @@ -46,7 +46,7 @@ protected: virtual void paintEvent(QPaintEvent* event) override; private: - AudioRenderBackend* backend_; + AudioPlaybackCache *playback_; QPixmap cached_waveform_; QSize cached_size_; diff --git a/app/widget/viewer/gizmotraverser.cpp b/app/widget/viewer/gizmotraverser.cpp index 1e77fd925..c8fdafe29 100644 --- a/app/widget/viewer/gizmotraverser.cpp +++ b/app/widget/viewer/gizmotraverser.cpp @@ -22,7 +22,7 @@ OLIVE_NAMESPACE_ENTER -void GizmoTraverser::FootageProcessingEvent(StreamPtr stream, const TimeRange &/*input_time*/, NodeValueTable *table) +void GizmoTraverser::FootageProcessingEvent(StreamPtr stream, const TimeRange &/*input_time*/, NodeValueTable *table) const { if (stream->type() == Stream::kVideo || stream->type() == Stream::kAudio) { diff --git a/app/widget/viewer/gizmotraverser.h b/app/widget/viewer/gizmotraverser.h index 97b2cfc43..9292f7b5d 100644 --- a/app/widget/viewer/gizmotraverser.h +++ b/app/widget/viewer/gizmotraverser.h @@ -31,7 +31,7 @@ public: GizmoTraverser() = default; protected: - virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) override; + virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) const override; }; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 097cca3aa..c39d3e2f2 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -100,14 +100,14 @@ ViewerWidget::ViewerWidget(QWidget *parent) : SetScale(48.0); // Start background renderers - video_renderer_ = new OpenGLBackend(this); - connect(video_renderer_, &VideoRenderBackend::CachedTimeReady, this, &ViewerWidget::RendererCachedTime); - connect(video_renderer_, &VideoRenderBackend::CachedTimeReady, ruler(), &TimeRuler::CacheTimeReady); - connect(video_renderer_, &VideoRenderBackend::RangeInvalidated, ruler(), &TimeRuler::CacheInvalidatedRange); - connect(video_renderer_, &VideoRenderBackend::GeneratedFrame, this, &ViewerWidget::RendererGeneratedFrame); - audio_renderer_ = new AudioBackend(this); + renderer_ = new OpenGLBackend(this); + /* + connect(renderer_, &RenderBackend::CachedTimeReady, this, &ViewerWidget::RendererCachedTime); + connect(renderer_, &RenderBackend::CachedTimeReady, ruler(), &TimeRuler::CacheTimeReady); + connect(renderer_, &RenderBackend::RangeInvalidated, ruler(), &TimeRuler::CacheInvalidatedRange); + connect(renderer_, &RenderBackend::GeneratedFrame, this, &ViewerWidget::RendererGeneratedFrame); + */ - waveform_view_->SetBackend(audio_renderer_); connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal); connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters); @@ -161,11 +161,10 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererParameters); connect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible); - connect(n, &ViewerOutput::VideoChangedBetween, this, &ViewerWidget::UpdateStack); - connect(n, &ViewerOutput::AudioChangedBetween, this, &ViewerWidget::UpdateStack); + connect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack); SizeChangedSlot(n->video_params().width(), n->video_params().height()); - LengthChangedSlot(n->Length()); + LengthChangedSlot(n->GetLength()); ColorManager* using_manager; if (override_color_manager_) { @@ -189,6 +188,7 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) UpdateStack(); if (GetConnectedTimelinePoints()) { + waveform_view_->SetViewer(GetConnectedNode()->audio_playback_cache()); waveform_view_->ConnectTimelinePoints(GetConnectedTimelinePoints()); } } @@ -204,8 +204,7 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererParameters); disconnect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible); - disconnect(n, &ViewerOutput::VideoChangedBetween, this, &ViewerWidget::UpdateStack); - disconnect(n, &ViewerOutput::AudioChangedBetween, this, &ViewerWidget::UpdateStack); + disconnect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack); // Effectively disables the viewer and clears the state SizeChangedSlot(0, 0); @@ -215,13 +214,13 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) window->display_widget()->DisconnectColorManager(); } + waveform_view_->SetViewer(nullptr); waveform_view_->ConnectTimelinePoints(nullptr); } void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n) { - video_renderer_->SetViewerNode(n); - audio_renderer_->SetViewerNode(n); + renderer_->SetViewerNode(n); } void ViewerWidget::ScaleChangedEvent(const double &s) @@ -332,16 +331,6 @@ void ViewerWidget::ForceUpdate() UpdateTextureFromNode(GetTime()); } -VideoRenderBackend *ViewerWidget::video_renderer() const -{ - return video_renderer_; -} - -ColorManager *ViewerWidget::color_manager() const -{ - return display_widget_->color_manager(); -} - void ViewerWidget::SetGizmos(Node *node) { display_widget_->SetTimeTarget(GetConnectedNode()); @@ -376,14 +365,14 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time) QString frame_fn = GetCachedFilenameFromTime(time); if (frame_fn.isEmpty()) { - video_renderer_->RenderFrame(time); + // FIXME: Connect QFutureWatcher to this + renderer_->RenderFrame(time); } else { FramePtr f = DecodeCachedImage(frame_fn); SetDisplayImage(f, false); } } else { SetDisplayImage(nullptr, false); - video_renderer_->UpdateLastRequestedTime(time); } } @@ -399,11 +388,11 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) playback_speed_ = speed; play_in_to_out_only_ = in_to_out_only; - QString audio_fn = audio_renderer_->CachePathName(); + QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetCacheFilename(); if (!audio_fn.isEmpty()) { - AudioManager::instance()->SetOutputParams(audio_renderer_->params()); + AudioManager::instance()->SetOutputParams(GetConnectedNode()->audio_playback_cache()->GetParameters()); AudioManager::instance()->StartOutput(audio_fn, - audio_renderer_->params().time_to_bytes(GetTime()), + GetConnectedNode()->audio_playback_cache()->GetParameters().time_to_bytes(GetTime()), playback_speed_); } @@ -433,17 +422,19 @@ void ViewerWidget::PushScrubbedAudio() { if (!IsPlaying() && Config::Current()["AudioScrubbing"].toBool()) { // Get audio src device from renderer - QString audio_fn = audio_renderer_->CachePathName(); + QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetCacheFilename(); QFile audio_src(audio_fn); if (audio_src.open(QFile::ReadOnly)) { + const AudioRenderingParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters(); + // FIXME: Hardcoded scrubbing interval (20ms) - int size_of_sample = audio_renderer_->params().time_to_bytes(rational(20, 1000)); + int size_of_sample = params.time_to_bytes(rational(20, 1000)); // Push audio - audio_src.seek(audio_renderer_->params().time_to_bytes(GetTime())); + audio_src.seek(params.time_to_bytes(GetTime())); QByteArray frame_audio = audio_src.read(size_of_sample); - AudioManager::instance()->SetOutputParams(audio_renderer_->params()); + AudioManager::instance()->SetOutputParams(params); AudioManager::instance()->PushToOutput(frame_audio); audio_src.close(); @@ -469,11 +460,11 @@ void ViewerWidget::UpdateMinimumScale() return; } - if (GetConnectedNode()->Length().isNull()) { + if (GetConnectedNode()->GetLength().isNull()) { // Avoids divide by zero SetMinimumScale(0); } else { - SetMinimumScale(static_cast(ruler()->width()) / GetConnectedNode()->Length().toDouble()); + SetMinimumScale(static_cast(ruler()->width()) / GetConnectedNode()->GetLength().toDouble()); } } @@ -526,7 +517,10 @@ void ViewerWidget::FillPlaybackQueue() QString ViewerWidget::GetCachedFilenameFromTime(const rational &time) { if (FrameExistsAtTime(time)) { - return video_renderer_->GetCachedFrame(time); + QByteArray hash = GetConnectedNode()->video_frame_cache()->GetHash(time); + return GetConnectedNode()->video_frame_cache()->CachePathName( + hash, + PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline)); } else { return QString(); } @@ -534,7 +528,7 @@ QString ViewerWidget::GetCachedFilenameFromTime(const rational &time) bool ViewerWidget::FrameExistsAtTime(const rational &time) { - return GetConnectedNode() && time < GetConnectedNode()->Length(); + return GetConnectedNode() && time < GetConnectedNode()->GetLength(); } FramePtr ViewerWidget::DecodeCachedImage(const QString &fn) @@ -683,25 +677,13 @@ void ViewerWidget::UpdateRendererParameters() RenderMode::Mode render_mode = RenderMode::kOffline; - VideoRenderingParams vparam(GetConnectedNode()->video_params(), - PixelFormat::instance()->GetConfiguredFormatForMode(render_mode), - render_mode, - divider_); + renderer_->SetDivider(divider_); + renderer_->SetMode(render_mode); + renderer_->SetPixelFormat(PixelFormat::instance()->GetConfiguredFormatForMode(render_mode)); - if (video_renderer_->params() != vparam) { - video_renderer_->SetParameters(vparam); - video_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length()), nullptr); - } + display_widget_->SetVideoParams(GetConnectedNode()->video_params()); - display_widget_->SetVideoParams(vparam); - - AudioRenderingParams aparam(GetConnectedNode()->audio_params(), - SampleFormat::kInternalFormat); - - if (audio_renderer_->params() != aparam) { - audio_renderer_->SetParameters(aparam); - audio_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length()), nullptr); - } + renderer_->SetSampleFormat(SampleFormat::kInternalFormat); } void ViewerWidget::ShowContextMenu(const QPoint &pos) @@ -925,7 +907,7 @@ void ViewerWidget::TimebaseChangedEvent(const rational &timebase) controls_->SetTimebase(timebase); controls_->SetTime(ruler()->GetTime()); - LengthChangedSlot(GetConnectedNode() ? GetConnectedNode()->Length() : 0); + LengthChangedSlot(GetConnectedNode() ? GetConnectedNode()->GetLength() : 0); } void ViewerWidget::PlaybackTimerUpdate() @@ -947,7 +929,7 @@ void ViewerWidget::PlaybackTimerUpdate() // Otherwise set the bounds to the range of the sequence min_time = 0; - max_time = Timecode::time_to_timestamp(GetConnectedNode()->Length(), timebase()); + max_time = Timecode::time_to_timestamp(GetConnectedNode()->GetLength(), timebase()); } } @@ -1039,8 +1021,10 @@ void ViewerWidget::SetZoomFromMenu(QAction *action) void ViewerWidget::InvalidateVisible(NodeInput* source) { - video_renderer_->InvalidateCache(TimeRange(GetTime(), GetTime()), source); - video_renderer_->RenderFrame(GetTime()); + /* + renderer_->NodeGraphChanged(source); + renderer_->RenderFrame(GetTime()); + */ } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 4fcd386bf..836e96d69 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -33,8 +33,6 @@ #include "node/output/viewer/viewer.h" #include "panel/scope/scope.h" #include "render/backend/opengl/openglbackend.h" -#include "render/backend/opengl/opengltexture.h" -#include "render/backend/audio/audiobackend.h" #include "viewerdisplay.h" #include "viewerplaybacktimer.h" #include "viewerqueue.h" @@ -87,9 +85,13 @@ public: void ForceUpdate(); - VideoRenderBackend* video_renderer() const; + RenderBackend* renderer() const { + return renderer_; + } - ColorManager* color_manager() const; + ColorManager* color_manager() const { + return display_widget_->color_manager(); + } void SetGizmos(Node* node); @@ -153,8 +155,7 @@ protected: virtual void resizeEvent(QResizeEvent *event) override; - OpenGLBackend* video_renderer_; - AudioBackend* audio_renderer_; + RenderBackend* renderer_; PlaybackControls* controls_; diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 1a88027d1..ba4046bba 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -113,7 +113,7 @@ void ViewerDisplayWidget::SetGizmos(Node *node) } } -void ViewerDisplayWidget::SetVideoParams(const VideoRenderingParams ¶ms) +void ViewerDisplayWidget::SetVideoParams(const VideoParams ¶ms) { gizmo_params_ = params; diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 0f0d2b81c..5f18cc303 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -72,7 +72,7 @@ public: void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin); void SetGizmos(Node* node); - void SetVideoParams(const VideoRenderingParams& params); + void SetVideoParams(const VideoParams ¶ms); void SetTime(const rational& time); FramePtr last_loaded_buffer() const; @@ -171,7 +171,7 @@ private: Node* gizmos_; NodeValueDatabase gizmo_db_; rational gizmo_drag_time_; - VideoRenderingParams gizmo_params_; + VideoParams gizmo_params_; bool gizmo_click_; rational time_; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 46e71c876..049fa20d8 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -493,8 +493,6 @@ TimelinePanel* MainWindow::AppendTimelinePanel() connect(panel, &TimelinePanel::SelectionChanged, node_panel_, &NodePanel::SelectBlocks); connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); - connect(sequence_viewer_panel_->video_renderer(), &VideoRenderBackend::CachedTimeReady, panel->ruler(), &TimeRuler::CacheTimeReady); - connect(sequence_viewer_panel_->video_renderer(), &VideoRenderBackend::RangeInvalidated, panel->ruler(), &TimeRuler::CacheInvalidatedRange); sequence_viewer_panel_->ConnectTimeBasedPanel(panel);