From 53ae25a7cbf55c357a3b652331e3971d892b92d5 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 26 Apr 2020 04:16:15 +1000 Subject: [PATCH] nodes: optimize node graph changes by only updating from inputs that changed A huge optimization that ensures only the parts of a node graph that have changed get pushed to the renderer. For thread-safety, the node graph is copied elsewhere so that users can make changes asynchronously and the graph can update when its threads are ready. Up until now, if an input value changed, every node's values would be re-copied, or worse, if a connection was changed, the entire graph would be recopied. This has been negligible in testing since we've been largely testing with small graphs, but for massive projects, it's important that this be as optimized as possible. --- app/dialog/export/export.cpp | 2 +- app/node/block/block.cpp | 6 +- app/node/block/block.h | 2 +- app/node/block/clip/clip.cpp | 10 +- app/node/block/clip/clip.h | 2 +- app/node/input.cpp | 52 +++- app/node/input.h | 12 +- app/node/input/media/media.cpp | 4 +- app/node/inputarray.cpp | 6 +- app/node/inputarray.h | 6 +- app/node/node.cpp | 193 +++++---------- app/node/node.h | 29 +-- app/node/output/track/track.cpp | 30 ++- app/node/output/track/track.h | 4 +- app/node/output/track/tracklist.cpp | 8 +- app/node/output/viewer/viewer.cpp | 27 +- app/node/output/viewer/viewer.h | 17 +- app/render/backend/audio/audiobackend.cpp | 14 +- app/render/backend/audio/audiobackend.h | 4 - app/render/backend/audiorenderbackend.cpp | 20 +- app/render/backend/audiorenderbackend.h | 6 - app/render/backend/exporter.cpp | 6 +- app/render/backend/opengl/openglbackend.cpp | 9 - app/render/backend/opengl/openglbackend.h | 4 - app/render/backend/renderbackend.cpp | 230 ++++++++++-------- app/render/backend/renderbackend.h | 26 +- app/render/backend/videorenderbackend.cpp | 2 - app/render/backend/videorenderworker.cpp | 5 +- app/widget/keyframeview/keyframeviewbase.cpp | 4 +- .../nodeparamviewwidgetbridge.cpp | 6 +- .../nodeparamview/nodeparamviewwidgetbridge.h | 2 +- app/widget/timelinewidget/undo/undo.cpp | 6 +- app/widget/viewer/viewer.cpp | 26 +- app/widget/viewer/viewer.h | 2 +- 34 files changed, 360 insertions(+), 422 deletions(-) diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index e079b9296..81b7f845b 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -229,7 +229,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : // 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())); + preview_viewer_->video_renderer()->InvalidateCache(TimeRange(0, viewer_node_->Length()), nullptr); progress_timer_.setInterval(1000); connect(&progress_timer_, &QTimer::timeout, this, &ExportDialog::UpdateTimeLabels); diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index ad314a946..ccb3f0140 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -40,7 +40,7 @@ Block::Block() : length_input_->SetConnectable(false); length_input_->set_is_keyframable(false); AddInput(length_input_); - connect(length_input_, SIGNAL(ValueChanged(const rational&, const rational&)), this, SLOT(LengthInputChanged())); + connect(length_input_, &NodeInput::ValueChanged, this, &Block::LengthInputChanged); media_in_input_ = new NodeInput("media_in_in", NodeParam::kRational); media_in_input_->SetConnectable(false); @@ -343,14 +343,14 @@ NodeInput *Block::speed_input() const return speed_input_; } -void Block::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from) +void Block::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source) { // We ignore length changes since they don't have an effect on our frames if (from == length_input_) { return; } - Node::InvalidateCache(start_range, end_range, from); + Node::InvalidateCache(range, from, source); } OLIVE_NAMESPACE_EXIT diff --git a/app/node/block/block.h b/app/node/block/block.h index a0feb41a9..26df9d13a 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -97,7 +97,7 @@ public: NodeInput* media_in_input() const; NodeInput* speed_input() const; - virtual void InvalidateCache(const rational& start_range, const rational& end_range, NodeInput* from = nullptr) override; + virtual void InvalidateCache(const TimeRange& range, NodeInput* from, NodeInput* source) override; public slots: diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index ebd286171..8db5efdd5 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -59,12 +59,12 @@ NodeInput *ClipBlock::texture_input() const return texture_input_; } -void ClipBlock::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from) +void ClipBlock::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source) { // If signal is from texture input, transform all times from media time to sequence time if (from == texture_input_) { - rational start = MediaToSequenceTime(start_range); - rational end = MediaToSequenceTime(end_range); + rational start = MediaToSequenceTime(range.in()); + rational end = MediaToSequenceTime(range.out()); // Ensure range actually covers this clip's area if (!(end < in() || start > out())) { @@ -73,12 +73,12 @@ void ClipBlock::InvalidateCache(const rational &start_range, const rational &end start = qMax(start, in()); end = qMin(end, out()); - Node::InvalidateCache(start, end, from); + Node::InvalidateCache(TimeRange(start, end), from, source); } } else { // Otherwise, pass signal along normally - Node::InvalidateCache(start_range, end_range, from); + Node::InvalidateCache(range, from, source); } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 6dc996b08..56f81c927 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -44,7 +44,7 @@ public: NodeInput* texture_input() const; - virtual void InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from = nullptr) override; + virtual void InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput* source) override; virtual TimeRange InputTimeAdjustment(NodeInput* input, const TimeRange& input_time) const override; diff --git a/app/node/input.cpp b/app/node/input.cpp index cdea0d1ed..e025f5ed7 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -60,7 +60,7 @@ NodeInput::NodeInput(const QString &id, const NodeParam::DataType &type) : Init(type); } -bool NodeInput::IsArray() +bool NodeInput::IsArray() const { return false; } @@ -273,7 +273,6 @@ void NodeInput::SaveConnections(QXmlStreamWriter *writer) const writer->writeEndElement(); // connections } - const NodeParam::DataType &NodeInput::data_type() const { return data_type_; @@ -363,6 +362,45 @@ QVariant NodeInput::StringToValue(const DataType& data_type, const QString &stri } } +void NodeInput::GetDependencies(QList &list, bool traverse, bool exclusive_only) const +{ + if (IsConnected() + && (get_connected_output()->edges().size() == 1 || !exclusive_only)) { + Node* connected = get_connected_node(); + + if (!list.contains(connected)) { + list.append(connected); + + if (traverse) { + QList connected_inputs = connected->GetInputsIncludingArrays(); + + foreach (NodeInput* i, connected_inputs) { + i->GetDependencies(list, traverse, exclusive_only); + } + } + } + } +} + +QList NodeInput::GetDependencies(bool traverse, bool exclusive_only) const +{ + QList list; + + GetDependencies(list, traverse, exclusive_only); + + return list; +} + +QList NodeInput::GetExclusiveDependencies() const +{ + return GetDependencies(true, true); +} + +QList NodeInput::GetImmediateDependencies() const +{ + return GetDependencies(false, false); +} + QVariant NodeInput::StringToValue(const QString &string, QList& footage_connections) { if (data_type_ == NodeParam::kFootage) { @@ -757,7 +795,7 @@ void NodeInput::KeyframeBezierInChanged() start = keyframe_tracks_.at(key->track()).at(keyframe_index - 1)->time(); } - emit ValueChanged(start, end); + emit ValueChanged(TimeRange(start, end)); } void NodeInput::KeyframeBezierOutChanged() @@ -772,7 +810,7 @@ void NodeInput::KeyframeBezierOutChanged() end = keyframe_tracks_.at(key->track()).at(keyframe_index + 1)->time(); } - emit ValueChanged(start, end); + emit ValueChanged(TimeRange(start, end)); } int NodeInput::FindIndexOfKeyframeFromRawPtr(NodeKeyframe *raw_ptr) const @@ -855,7 +893,7 @@ TimeRange NodeInput::get_range_around_index(int index, int track) const void NodeInput::emit_time_range(const TimeRange &range) { - emit ValueChanged(range.in(), range.out()); + emit ValueChanged(range); } void NodeInput::emit_range_affected_by_keyframe(NodeKeyframe *key) @@ -915,7 +953,7 @@ void NodeInput::set_standard_value(const QVariant &value, int track) if (is_using_standard_value(track)) { // If this standard value is being used, we need to send a value changed signal - emit ValueChanged(RATIONAL_MIN, RATIONAL_MAX); + emit ValueChanged(TimeRange(RATIONAL_MIN, RATIONAL_MAX)); } } @@ -964,7 +1002,7 @@ void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_conn } } - emit dest->ValueChanged(RATIONAL_MIN, RATIONAL_MAX); + emit dest->ValueChanged(TimeRange(RATIONAL_MIN, RATIONAL_MAX)); } void NodeInput::set_property(const QString &key, const QVariant &value) diff --git a/app/node/input.h b/app/node/input.h index 6d1910623..c672907ee 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -49,7 +49,7 @@ public: NodeInput(const QString &id, const DataType& type, const QVariant& default_value); NodeInput(const QString &id, const DataType& type); - virtual bool IsArray(); + virtual bool IsArray() const; /** * @brief Returns kInput @@ -270,8 +270,16 @@ public: static QVariant StringToValue(const DataType &data_type, const QString &string); + void GetDependencies(QList& list, bool traverse, bool exclusive_only) const; + + QList GetDependencies(bool traverse = true, bool exclusive_only = false) const; + + QList GetExclusiveDependencies() const; + + QList GetImmediateDependencies() const; + signals: - void ValueChanged(const rational& start, const rational& end); + void ValueChanged(const TimeRange& range); void KeyframeEnableChanged(bool); diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index a69207ab6..ff371da53 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -31,7 +31,7 @@ MediaInput::MediaInput() : footage_input_ = new NodeInput("footage_in", NodeInput::kFootage); footage_input_->SetConnectable(false); footage_input_->set_is_keyframable(false); - connect(footage_input_, SIGNAL(ValueChanged(const rational&, const rational&)), this, SLOT(FootageChanged())); + connect(footage_input_, &NodeInput::ValueChanged, this, &MediaInput::FootageChanged); AddInput(footage_input_); } @@ -96,7 +96,7 @@ void MediaInput::FootageChanged() void MediaInput::FootageParametersChanged() { - InvalidateCache(0, RATIONAL_MAX, footage_input_); + InvalidateCache(TimeRange(0, RATIONAL_MAX), footage_input_, footage_input_); } OLIVE_NAMESPACE_EXIT diff --git a/app/node/inputarray.cpp b/app/node/inputarray.cpp index 4cea15ac2..42789b7d6 100644 --- a/app/node/inputarray.cpp +++ b/app/node/inputarray.cpp @@ -33,7 +33,7 @@ NodeInputArray::NodeInputArray(const QString &id, const DataType &type, const QV { } -bool NodeInputArray::IsArray() +bool NodeInputArray::IsArray() const { return true; } @@ -79,8 +79,8 @@ void NodeInputArray::SetSize(int size) sub_params_.replace(i, new_param); connect(new_param, &NodeInput::ValueChanged, this, &NodeInput::ValueChanged); - connect(new_param, &NodeInput::EdgeAdded, this, &NodeInput::EdgeAdded); - connect(new_param, &NodeInput::EdgeRemoved, this, &NodeInput::EdgeRemoved); + connect(new_param, &NodeInput::EdgeAdded, this, &NodeInputArray::SubParamEdgeAdded); + connect(new_param, &NodeInput::EdgeRemoved, this, &NodeInputArray::SubParamEdgeRemoved); } } diff --git a/app/node/inputarray.h b/app/node/inputarray.h index d43300e22..8a935f73e 100644 --- a/app/node/inputarray.h +++ b/app/node/inputarray.h @@ -31,7 +31,7 @@ class NodeInputArray : public NodeInput public: NodeInputArray(const QString &id, const DataType& type, const QVariant& default_value = 0); - virtual bool IsArray() override; + virtual bool IsArray() const override; int GetSize() const; @@ -54,6 +54,10 @@ public: signals: void SizeChanged(int size); + void SubParamEdgeAdded(NodeEdgePtr edge); + + void SubParamEdgeRemoved(NodeEdgePtr edge); + protected: virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data, const QAtomicInt *cancelled) override; diff --git a/app/node/node.cpp b/app/node/node.cpp index ae681ecf1..ffde76468 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -75,7 +75,13 @@ void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const QAto continue; } - NodeParam* param = GetParameterWithID(param_id); + NodeParam* param; + + if (reader->name() == QStringLiteral("input")) { + param = GetInputWithID(param_id); + } else { + param = GetOutputWithID(param_id); + } if (!param) { qDebug() << "No parameter in" << id() << "with parameter" << param_id; @@ -153,21 +159,21 @@ NodeValueTable Node::Value(NodeValueDatabase &value) const return value.Merge(); } -void Node::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from) +void Node::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source) { Q_UNUSED(from) - SendInvalidateCache(start_range, end_range); + SendInvalidateCache(range, source); } -void Node::InvalidateVisible(NodeInput *from) +void Node::InvalidateVisible(NodeInput *from, NodeInput* source) { Q_UNUSED(from) foreach (NodeParam* param, params_) { if (param->type() == NodeParam::kOutput) { foreach (NodeEdgePtr edge, param->edges()) { - edge->input()->parentNode()->InvalidateVisible(edge->input()); + edge->input()->parentNode()->InvalidateVisible(edge->input(), source); } } } @@ -185,7 +191,7 @@ TimeRange Node::OutputTimeAdjustment(NodeInput *, const TimeRange &input_time) c return input_time; } -void Node::SendInvalidateCache(const rational &start_range, const rational &end_range) +void Node::SendInvalidateCache(const TimeRange &range, NodeInput *source) { // Loop through all parameters (there should be no children that are not NodeParams) foreach (NodeParam* param, params_) { @@ -199,25 +205,7 @@ void Node::SendInvalidateCache(const rational &start_range, const rational &end_ Node* connected_node = connected_input->parentNode(); // Send clear cache signal to the Node - connected_node->InvalidateCache(start_range, end_range, connected_input); - } - } - } -} - -void Node::DependentEdgeChanged(NodeInput *from) -{ - Q_UNUSED(from) - - foreach (NodeParam* p, params_) { - if (p->type() == NodeParam::kOutput && p->IsConnected()) { - NodeOutput* out = static_cast(p); - - foreach (NodeEdgePtr edge, out->edges()) { - NodeInput* connected_input = edge->input(); - Node* connected_node = connected_input->parentNode(); - - connected_node->DependentEdgeChanged(connected_input); + connected_node->InvalidateCache(range, connected_input, source); } } } @@ -300,57 +288,6 @@ void Node::CopyInputs(Node *source, Node *destination, bool include_connections) } } -void DuplicateConnectionsBetweenListsInternal(const QList &source, const QList &destination, NodeInput* source_input, NodeInput* dest_input) -{ - if (source_input->IsConnected()) { - // Get this input's connected outputs - NodeOutput* source_output = source_input->get_connected_output(); - Node* source_output_node = source_output->parentNode(); - - // Find equivalent in destination list - Node* dest_output_node = destination.at(source.indexOf(source_output_node)); - - Q_ASSERT(dest_output_node->id() == source_output_node->id()); - - NodeOutput* dest_output = static_cast(dest_output_node->GetParameterWithID(source_output->id())); - - NodeParam::ConnectEdge(dest_output, dest_input); - } - - // If inputs are arrays, duplicate their connections too - if (source_input->IsArray()) { - NodeInputArray* source_array = static_cast(source_input); - NodeInputArray* dest_array = static_cast(dest_input); - - for (int i=0;iGetSize();i++) { - DuplicateConnectionsBetweenListsInternal(source, destination, source_array->At(i), dest_array->At(i)); - } - } -} - -void Node::DuplicateConnectionsBetweenLists(const QList &source, const QList &destination) -{ - Q_ASSERT(source.size() == destination.size()); - - for (int i=0;iid() == dest_input_node->id()); - - for (int j=0;jparams_.size();j++) { - NodeParam* source_param = source_input_node->params_.at(j); - - if (source_param->type() == NodeInput::kInput) { - NodeInput* source_input = static_cast(source_param); - NodeInput* dest_input = static_cast(dest_input_node->params_.at(j)); - - DuplicateConnectionsBetweenListsInternal(source, destination, source_input, dest_input); - } - } - } -} - bool Node::CanBeDeleted() const { return can_be_deleted_; @@ -381,29 +318,6 @@ int Node::IndexOfParameter(NodeParam *param) const return params_.indexOf(param); } -void Node::TraverseInputInternal(QList& list, NodeInput* input, bool traverse, bool exclusive_only) { - if (input->IsConnected() - && (input->get_connected_output()->edges().size() == 1 || !exclusive_only)) { - Node* connected = input->get_connected_node(); - - if (!list.contains(connected)) { - list.append(connected); - - if (traverse) { - GetDependenciesInternal(connected, list, traverse, exclusive_only); - } - } - } - - if (input->IsArray()) { - NodeInputArray* input_array = static_cast(input); - - for (int i=0;iGetSize();i++) { - TraverseInputInternal(list, input_array->At(i), traverse, exclusive_only); - } - } -} - /** * @brief Recursively collects dependencies of Node `n` and appends them to QList `list` * @@ -412,41 +326,30 @@ void Node::TraverseInputInternal(QList& list, NodeInput* input, bool trav * TRUE to recursively traverse each node for a complete dependency graph. FALSE to return only the immediate * dependencies. */ -void Node::GetDependenciesInternal(const Node* n, QList& list, bool traverse, bool exclusive_only) { - foreach (NodeParam* p, n->parameters()) { - if (p->type() == NodeParam::kInput) { - NodeInput* input = static_cast(p); +QList Node::GetDependenciesInternal(bool traverse, bool exclusive_only) const { + QList inputs = GetInputsIncludingArrays(); + QList list; - TraverseInputInternal(list, input, traverse, exclusive_only); - } + foreach (NodeInput* i, inputs) { + i->GetDependencies(list, traverse, exclusive_only); } + + return list; } QList Node::GetDependencies() const { - QList node_list; - - GetDependenciesInternal(this, node_list, true, false); - - return node_list; + return GetDependenciesInternal(true, false); } QList Node::GetExclusiveDependencies() const { - QList node_list; - - GetDependenciesInternal(this, node_list, true, true); - - return node_list; + return GetDependenciesInternal(true, true); } QList Node::GetImmediateDependencies() const { - QList node_list; - - GetDependenciesInternal(this, node_list, false, false); - - return node_list; + return GetDependenciesInternal(false, false); } Node::Capabilities Node::GetCapabilities(const NodeValueDatabase &) const @@ -479,7 +382,7 @@ NodeInput *Node::ShaderIterativeInput() const return nullptr; } -NodeInput* Node::ProcessesSamplesFrom(const NodeValueDatabase &value) const +NodeInput* Node::ProcessesSamplesFrom(const NodeValueDatabase &) const { return nullptr; } @@ -488,11 +391,25 @@ void Node::ProcessSamples(const NodeValueDatabase &, const AudioRenderingParams& { } -NodeParam *Node::GetParameterWithID(const QString &id) const +NodeInput *Node::GetInputWithID(const QString &id) const { - foreach (NodeParam* param, params_) { - if (param->id() == id) { - return param; + QList inputs = GetInputsIncludingArrays(); + + foreach (NodeInput* i, inputs) { + if (i->id() == id) { + return i; + } + } + + return nullptr; +} + +NodeOutput *Node::GetOutputWithID(const QString &id) const +{ + foreach (NodeParam* p, params_) { + if (p->type() == NodeParam::kOutput + && p->id() == id) { + return static_cast(p); } } @@ -665,25 +582,41 @@ void Node::ConnectInput(NodeInput *input) connect(input, &NodeInput::ValueChanged, this, &Node::InputChanged); connect(input, &NodeInput::EdgeAdded, this, &Node::InputConnectionChanged); connect(input, &NodeInput::EdgeRemoved, this, &Node::InputConnectionChanged); + + if (input->IsArray()) { + NodeInputArray* array = static_cast(input); + + connect(array, &NodeInputArray::SubParamEdgeAdded, this, &Node::InputConnectionChanged); + connect(array, &NodeInputArray::SubParamEdgeRemoved, this, &Node::InputConnectionChanged); + connect(array, &NodeInputArray::SubParamEdgeAdded, this, &Node::EdgeAdded); + connect(array, &NodeInputArray::SubParamEdgeRemoved, this, &Node::EdgeRemoved); + } } void Node::DisconnectInput(NodeInput *input) { + if (input->IsArray()) { + NodeInputArray* array = static_cast(input); + + disconnect(array, &NodeInputArray::SubParamEdgeAdded, this, &Node::InputConnectionChanged); + disconnect(array, &NodeInputArray::SubParamEdgeRemoved, this, &Node::InputConnectionChanged); + disconnect(array, &NodeInputArray::SubParamEdgeAdded, this, &Node::EdgeAdded); + disconnect(array, &NodeInputArray::SubParamEdgeRemoved, this, &Node::EdgeRemoved); + } + disconnect(input, &NodeInput::ValueChanged, this, &Node::InputChanged); disconnect(input, &NodeInput::EdgeAdded, this, &Node::InputConnectionChanged); disconnect(input, &NodeInput::EdgeRemoved, this, &Node::InputConnectionChanged); } -void Node::InputChanged(rational start, rational end) +void Node::InputChanged(const TimeRange& range) { - InvalidateCache(start, end, static_cast(sender())); + InvalidateCache(range, static_cast(sender()), static_cast(sender())); } void Node::InputConnectionChanged(NodeEdgePtr edge) { - DependentEdgeChanged(edge->input()); - - InvalidateCache(RATIONAL_MIN, RATIONAL_MAX, static_cast(sender())); + InvalidateCache(TimeRange(RATIONAL_MIN, RATIONAL_MAX), edge->input(), edge->input()); } OLIVE_NAMESPACE_EXIT diff --git a/app/node/node.h b/app/node/node.h index 0ec7e06eb..16b33c8c1 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -29,7 +29,6 @@ #include "codec/samplebuffer.h" #include "common/rational.h" #include "common/xmlutils.h" -#include "node/dependency.h" #include "node/input.h" #include "node/inputarray.h" #include "node/output.h" @@ -196,9 +195,14 @@ public: virtual void ProcessSamples(const NodeValueDatabase &values, const AudioRenderingParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const; /** - * @brief Returns the parameter with the specified ID (or nullptr if it doesn't exist) + * @brief Returns the input with the specified ID (or nullptr if it doesn't exist) */ - NodeParam* GetParameterWithID(const QString& id) const; + NodeInput* GetInputWithID(const QString& id) const; + + /** + * @brief Returns the output with the specified ID (or nullptr if it doesn't exist) + */ + NodeOutput* GetOutputWithID(const QString& id) const; /** * @brief Returns whether this Node outputs data to the Node `n` in any way @@ -261,12 +265,12 @@ public: * the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can * call this function with transformed time and relay the signal that way. */ - virtual void InvalidateCache(const rational& start_range, const rational& end_range, NodeInput* from = nullptr); + virtual void InvalidateCache(const TimeRange& range, NodeInput* from, NodeInput* source); /** * @brief Signal through node graph to only invalidate frames that are currently visible on a ViewerWidget */ - virtual void InvalidateVisible(NodeInput *from); + virtual void InvalidateVisible(NodeInput *from, NodeInput* source); /** * @brief Adjusts time that should be sent to nodes connected to certain inputs. @@ -288,11 +292,6 @@ public: */ static void CopyInputs(Node* source, Node* destination, bool include_connections = true); - /** - * @brief For a list of copies nodes, this function will duplicate all the connections in the source list to the destination list - */ - static void DuplicateConnectionsBetweenLists(const QList& source, const QList &destination); - /** * @brief Return whether this Node can be deleted or not */ @@ -358,9 +357,7 @@ protected: void ClearCachedValuesInParameters(const rational& start_range, const rational& end_range); - void SendInvalidateCache(const rational& start_range, const rational& end_range); - - virtual void DependentEdgeChanged(NodeInput* from); + void SendInvalidateCache(const TimeRange &range, NodeInput *source); virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data); @@ -403,9 +400,7 @@ private: void DisconnectInput(NodeInput* input); - static void TraverseInputInternal(QList& list, NodeInput* input, bool traverse, bool exclusive_only); - - static void GetDependenciesInternal(const Node* n, QList& list, bool traverse, bool exclusive_only); + QList GetDependenciesInternal(bool traverse, bool exclusive_only) const; QList params_; @@ -425,7 +420,7 @@ private: QPointF position_; private slots: - void InputChanged(rational start, rational end); + void InputChanged(const TimeRange &range); void InputConnectionChanged(NodeEdgePtr edge); diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 3e8df213d..aeb6be27c 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -38,8 +38,8 @@ TrackOutput::TrackOutput() : block_input_ = new NodeInputArray("block_in", NodeParam::kAny); block_input_->set_is_keyframable(false); AddInput(block_input_); - connect(block_input_, &NodeInputArray::EdgeAdded, this, &TrackOutput::BlockConnected); - connect(block_input_, &NodeInputArray::EdgeRemoved, this, &TrackOutput::BlockDisconnected); + connect(block_input_, &NodeInputArray::SubParamEdgeAdded, this, &TrackOutput::BlockConnected); + connect(block_input_, &NodeInputArray::SubParamEdgeRemoved, this, &TrackOutput::BlockDisconnected); connect(block_input_, &NodeInputArray::SizeChanged, this, &TrackOutput::BlockListSizeChanged); muted_input_ = new NodeInput("muted_in", NodeParam::kBoolean); @@ -236,10 +236,10 @@ const QVector &TrackOutput::Blocks() const return block_cache_; } -void TrackOutput::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from) +void TrackOutput::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source) { if (block_invalidate_cache_stack_ == 0) { - Node::InvalidateCache(qMax(start_range, rational(0)), qMin(end_range, track_length()), from); + Node::InvalidateCache(TimeRange(qMax(range.in(), rational(0)), qMin(range.out(), track_length())), from, source); } } @@ -276,7 +276,7 @@ void TrackOutput::InsertBlockAtIndex(Block *block, int index) UnblockInvalidateCache(); - InvalidateCache(block->in(), track_length()); + InvalidateCache(TimeRange(block->in(), track_length()), block_input_, block_input_); } void TrackOutput::AppendBlock(Block *block) @@ -291,7 +291,7 @@ void TrackOutput::AppendBlock(Block *block) UnblockInvalidateCache(); // Invalidate area that block was added to - InvalidateCache(block->in(), track_length()); + InvalidateCache(TimeRange(block->in(), track_length()), block_input_, block_input_); } void TrackOutput::BlockInvalidateCache() @@ -316,7 +316,7 @@ void TrackOutput::RippleRemoveBlock(Block *block) UnblockInvalidateCache(); - InvalidateCache(remove_in, track_length()); + InvalidateCache(TimeRange(remove_in, track_length()), block_input_, block_input_); } void TrackOutput::ReplaceBlock(Block *old, Block *replace) @@ -334,9 +334,9 @@ void TrackOutput::ReplaceBlock(Block *old, Block *replace) UnblockInvalidateCache(); if (old->length() == replace->length()) { - InvalidateCache(replace->in(), replace->out()); + InvalidateCache(TimeRange(replace->in(), replace->out()), block_input_, block_input_); } else { - InvalidateCache(replace->in(), RATIONAL_MAX); + InvalidateCache(TimeRange(replace->in(), RATIONAL_MAX), block_input_, block_input_); } } @@ -407,6 +407,11 @@ bool TrackOutput::IsLocked() const return locked_; } +NodeInputArray *TrackOutput::block_input() const +{ + return block_input_; +} + void TrackOutput::SetTrackName(const QString &name) { track_name_ = name; @@ -415,7 +420,7 @@ void TrackOutput::SetTrackName(const QString &name) void TrackOutput::SetMuted(bool e) { muted_input_->set_standard_value(e); - InvalidateCache(0, track_length()); + InvalidateCache(TimeRange(0, track_length()), block_input_, block_input_); } void TrackOutput::SetLocked(bool e) @@ -462,8 +467,9 @@ void TrackOutput::UpdateInOutFrom(int index) track_length_ = new_track_length; emit TrackLengthChanged(); - InvalidateCache(qMin(old_track_length, new_track_length), - qMax(old_track_length, new_track_length)); + InvalidateCache(TimeRange(qMin(old_track_length, new_track_length), qMax(old_track_length, new_track_length)), + block_input_, + block_input_); } } diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 9ff9df009..11eec0f7b 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -84,7 +84,7 @@ public: const QVector& Blocks() const; - virtual void InvalidateCache(const rational& start_range, const rational& end_range, NodeInput* from = nullptr) override; + virtual void InvalidateCache(const TimeRange& range, NodeInput* from, NodeInput *source) override; /** * @brief Adds Block `block` at the very beginning of the Sequence before all other clips @@ -150,6 +150,8 @@ public: bool IsLocked() const; + NodeInputArray* block_input() const; + public slots: void SetTrackName(const QString& name); diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index 05f9a6e6f..b3246f257 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -31,8 +31,8 @@ TrackList::TrackList(ViewerOutput *parent, const Timeline::TrackType &type, Node track_input_(track_input), type_(type) { - connect(track_input, &NodeInputArray::EdgeAdded, this, &TrackList::TrackConnected); - connect(track_input, &NodeInputArray::EdgeRemoved, this, &TrackList::TrackDisconnected); + connect(track_input, &NodeInputArray::SubParamEdgeAdded, this, &TrackList::TrackConnected); + connect(track_input, &NodeInputArray::SubParamEdgeRemoved, this, &TrackList::TrackDisconnected); connect(track_input, &NodeInputArray::SizeChanged, this, &TrackList::TrackListSizeChanged); } @@ -120,8 +120,8 @@ TrackOutput* TrackList::AddTrack() Node* blend = NodeFactory::CreateFromID(QStringLiteral("org.olivevideoeditor.Olive.alphaoverblend")); GetParentGraph()->AddNode(blend); - NodeParam::ConnectEdge(track->output(), static_cast(blend->GetParameterWithID("blend_in"))); - NodeParam::ConnectEdge(last_track->output(), static_cast(blend->GetParameterWithID("base_in"))); + NodeParam::ConnectEdge(track->output(), static_cast(blend->GetInputWithID("blend_in"))); + NodeParam::ConnectEdge(last_track->output(), static_cast(blend->GetInputWithID("base_in"))); NodeParam::ConnectEdge(blend->output(), edge->input()); break; } diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index d8d472371..665f287f6 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -92,26 +92,24 @@ NodeInput *ViewerOutput::samples_input() const return samples_input_; } -void ViewerOutput::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from) +void ViewerOutput::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source) { - Node::InvalidateCache(start_range, end_range, from); - if (from == texture_input()) { - emit VideoChangedBetween(TimeRange(start_range, end_range)); + emit VideoChangedBetween(range, source); } else if (from == samples_input()) { - emit AudioChangedBetween(TimeRange(start_range, end_range)); + emit AudioChangedBetween(range, source); } - SendInvalidateCache(start_range, end_range); + Node::InvalidateCache(range, from, source); } -void ViewerOutput::InvalidateVisible(NodeInput* from) +void ViewerOutput::InvalidateVisible(NodeInput* from, NodeInput *source) { if (from == texture_input()) { - emit VisibleInvalidated(); + emit VisibleInvalidated(source); } - Node::InvalidateVisible(from); + Node::InvalidateVisible(from, source); } const VideoParams &ViewerOutput::video_params() const @@ -164,17 +162,6 @@ const QUuid &ViewerOutput::uuid() const return uuid_; } -void ViewerOutput::DependentEdgeChanged(NodeInput *from) -{ - if (from == texture_input_) { - emit VideoGraphChanged(); - } else if (from == samples_input_) { - emit AudioGraphChanged(); - } - - Node::DependentEdgeChanged(from); -} - void ViewerOutput::UpdateTrackCache() { track_cache_.clear(); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 95afbce70..57101b67a 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -55,8 +55,8 @@ public: NodeInput* texture_input() const; NodeInput* samples_input() const; - virtual void InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from = nullptr) override; - virtual void InvalidateVisible(NodeInput *from) override; + 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; @@ -79,21 +79,14 @@ public: const QString& media_name() const; void set_media_name(const QString& name); -protected: - virtual void DependentEdgeChanged(NodeInput* from) override; - signals: void TimebaseChanged(const rational&); - void VideoChangedBetween(const TimeRange& range); + void VideoChangedBetween(const TimeRange& range, NodeInput* source); - void AudioChangedBetween(const TimeRange& range); + void AudioChangedBetween(const TimeRange& range, NodeInput* source); - void VisibleInvalidated(); - - void VideoGraphChanged(); - - void AudioGraphChanged(); + void VisibleInvalidated(NodeInput* source); void LengthChanged(const rational& length); diff --git a/app/render/backend/audio/audiobackend.cpp b/app/render/backend/audio/audiobackend.cpp index 07c023187..6b6f81ab2 100644 --- a/app/render/backend/audio/audiobackend.cpp +++ b/app/render/backend/audio/audiobackend.cpp @@ -39,7 +39,7 @@ bool AudioBackend::InitInternal() // Initiate one thread per CPU core for (int i=0;iSetParameters(params()); processors_.append(processor); } @@ -51,18 +51,6 @@ void AudioBackend::CloseInternal() { } -bool AudioBackend::CompileInternal() -{ - // This backend doesn't compile anything yet - return AudioRenderBackend::CompileInternal(); -} - -void AudioBackend::DecompileInternal() -{ - // This backend doesn't compile anything yet - AudioRenderBackend::DecompileInternal(); -} - void AudioBackend::ConnectWorkerToThis(RenderWorker *worker) { AudioRenderBackend::ConnectWorkerToThis(worker); diff --git a/app/render/backend/audio/audiobackend.h b/app/render/backend/audio/audiobackend.h index 81b423c68..1eaa2941d 100644 --- a/app/render/backend/audio/audiobackend.h +++ b/app/render/backend/audio/audiobackend.h @@ -40,10 +40,6 @@ protected: virtual void CloseInternal() override; - virtual bool CompileInternal() override; - - virtual void DecompileInternal() override; - virtual void ConnectWorkerToThis(RenderWorker* worker) override; private slots: diff --git a/app/render/backend/audiorenderbackend.cpp b/app/render/backend/audiorenderbackend.cpp index d5226945a..b4df5f4ba 100644 --- a/app/render/backend/audiorenderbackend.cpp +++ b/app/render/backend/audiorenderbackend.cpp @@ -58,31 +58,15 @@ void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms) void AudioRenderBackend::ConnectViewer(ViewerOutput *node) { connect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache); - connect(node, &ViewerOutput::AudioGraphChanged, this, &AudioRenderBackend::QueueRecompile); connect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache); } void AudioRenderBackend::DisconnectViewer(ViewerOutput *node) { disconnect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache); - disconnect(node, &ViewerOutput::AudioGraphChanged, this, &AudioRenderBackend::QueueRecompile); disconnect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache); } -bool AudioRenderBackend::CompileInternal() -{ - for (int i=0;ihas_conformed_version(params)) { // Index JUST finished, requeue this time - InvalidateCache(range); + InvalidateCache(range, nullptr); } else { @@ -215,7 +199,7 @@ void AudioRenderBackend::ConformUpdated(Stream *stream, AudioRenderingParams par // Send invalidate cache signal ic_from_conform_ = true; - InvalidateCache(copy.affected_range); + InvalidateCache(copy.affected_range, nullptr); ic_from_conform_ = false; } diff --git a/app/render/backend/audiorenderbackend.h b/app/render/backend/audiorenderbackend.h index f2420079d..77a774915 100644 --- a/app/render/backend/audiorenderbackend.h +++ b/app/render/backend/audiorenderbackend.h @@ -54,10 +54,6 @@ protected: virtual void DisconnectViewer(ViewerOutput* node) override; - virtual bool CompileInternal() override; - - virtual void DecompileInternal() override; - /** * @brief Internal function for generating the cache ID */ @@ -73,8 +69,6 @@ protected: virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range) override; - QHash copy_map_; - private: struct ConformWaitInfo { StreamPtr stream; diff --git a/app/render/backend/exporter.cpp b/app/render/backend/exporter.cpp index 5b92a663f..aa8af3165 100644 --- a/app/render/backend/exporter.cpp +++ b/app/render/backend/exporter.cpp @@ -291,14 +291,14 @@ void Exporter::EncoderOpenedSuccessfully() video_backend_->SetOperatingMode(VideoRenderWorker::kHashOnly); connect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete); - video_backend_->InvalidateCache(export_range_); + video_backend_->InvalidateCache(export_range_, nullptr); } if (!audio_done_) { // We set the audio backend to render the full sequence to the disk connect(audio_backend_, &AudioRenderBackend::AudioComplete, this, &Exporter::AudioRendered); - audio_backend_->InvalidateCache(export_range_); + audio_backend_->InvalidateCache(export_range_, nullptr); } } @@ -344,7 +344,7 @@ void Exporter::VideoHashesComplete() } foreach (const TimeRange& range, ranges) { - video_backend_->InvalidateCache(range); + video_backend_->InvalidateCache(range, nullptr); } } diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index 2db0cefa6..e1144dc5d 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -89,15 +89,6 @@ void OpenGLBackend::CloseInternal() VideoRenderBackend::CloseInternal(); } -bool OpenGLBackend::CompileInternal() -{ - return true; -} - -void OpenGLBackend::DecompileInternal() -{ -} - void OpenGLBackend::ParamsChangedEvent() { // If we're initiated, we need to recreate the texture. Otherwise this backend isn't active so it doesn't matter. diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/backend/opengl/openglbackend.h index 59fc34465..62945ffa1 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/backend/opengl/openglbackend.h @@ -43,10 +43,6 @@ protected: virtual void CloseInternal() override; - virtual bool CompileInternal() override; - - virtual void DecompileInternal() override; - virtual void ParamsChangedEvent() override; private: diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 0cfb0eae4..310a85431 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -31,12 +31,9 @@ OLIVE_NAMESPACE_ENTER RenderBackend::RenderBackend(QObject *parent) : QObject(parent), - compiled_(false), started_(false), viewer_node_(nullptr), - copied_viewer_node_(nullptr), - recompile_queued_(false), - input_update_queued_(false) + copied_viewer_node_(nullptr) { // FIXME: Don't create in CLI mode cancel_dialog_ = new RenderCancelDialog(Core::instance()->main_window()); @@ -84,7 +81,7 @@ void RenderBackend::Close() CancelQueue(); - Decompile(); + SetViewerNode(nullptr); CloseInternal(); @@ -114,23 +111,30 @@ const QString &RenderBackend::GetError() const void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) { - if (viewer_node_ != nullptr) { + if (viewer_node_) { CancelQueue(); DisconnectViewer(viewer_node_); - Decompile(); + copied_graph_.Clear(); + copied_viewer_node_ = nullptr; + node_copy_map_.clear(); } viewer_node_ = viewer_node; - if (viewer_node_ != nullptr) { + if (viewer_node_) { ConnectViewer(viewer_node_); RegenerateCacheID(); - } - InvalidateCache(TimeRange(0, RATIONAL_MAX)); + copied_viewer_node_ = static_cast(viewer_node_->copy()); + copied_graph_.AddNode(copied_viewer_node_); + node_copy_map_.insert(viewer_node_, copied_viewer_node_); + + InvalidateCache(TimeRange(0, RATIONAL_MAX), + static_cast(viewer_node_->GetInputWithID(GetDependentInput()->id()))); + } } bool RenderBackend::IsInitiated() @@ -138,58 +142,6 @@ bool RenderBackend::IsInitiated() return started_; } -bool RenderBackend::Compile() -{ - if (compiled_) { - return true; - } - - // Get dependencies of viewer node - source_node_list_.append(viewer_node_); - source_node_list_.append(viewer_node_->GetDependencies()); - - // Copy all dependencies into graph - foreach (Node* n, source_node_list_) { - Node* copy = n->copy(); - - Node::CopyInputs(n, copy, false); - - copied_graph_.AddNode(copy); - } - - // We just copied the inputs, so if an input update is queued, it's unnecessary - input_update_queued_ = false; - - // We know that the first node will be the viewer node since we appended that first in the copy - copied_viewer_node_ = static_cast(copied_graph_.nodes().first()); - - // Copy connections - Node::DuplicateConnectionsBetweenLists(source_node_list_, copied_graph_.nodes()); - - compiled_ = CompileInternal(); - - if (!compiled_) { - Decompile(); - } - - return compiled_; -} - -void RenderBackend::Decompile() -{ - if (!compiled_) { - return; - } - - DecompileInternal(); - - copied_graph_.Clear(); - copied_viewer_node_ = nullptr; - source_node_list_.clear(); - - compiled_ = false; -} - void RenderBackend::RegenerateCacheID() { QCryptographicHash hash(QCryptographicHash::Sha1); @@ -261,34 +213,19 @@ void RenderBackend::CacheNext() return; } - if (!Init() - || !ViewerIsConnected() - || !CanRender()) { + if (!ViewerIsConnected() + || !CanRender() + || !Init()) { return; } - if ((input_update_queued_ || recompile_queued_) && !AllProcessorsAreAvailable()) { - return; - } - - if (recompile_queued_) { - Decompile(); - recompile_queued_ = false; - } - - if (!compiled_ && !Compile()) { - return; - } - - if (input_update_queued_) { - for (int i=0;iget_connected_node(); @@ -356,12 +293,8 @@ void RenderBackend::CancelQueue() cancel_dialog_->RunIfWorkersAreBusy(); } -void RenderBackend::InvalidateCache(const TimeRange &range) +void RenderBackend::InvalidateCache(const TimeRange &range, NodeInput *from) { - if (!CanRender()) { - return; - } - // Adjust range to min/max values rational start_range_adj = qMax(rational(0), range.in()); rational end_range_adj = qMin(GetSequenceLength(), range.out()); @@ -371,15 +304,18 @@ void RenderBackend::InvalidateCache(const TimeRange &range) << "and" << end_range_adj.toDouble(); - // Queue value update - QueueValueUpdate(); + if (from) { + // Queue value update + qDebug() << " from" << from->parentNode()->id() << "::" << from->id(); + QueueValueUpdate(from); + } InvalidateCacheInternal(start_range_adj, end_range_adj); } bool RenderBackend::ViewerIsConnected() const { - return viewer_node_ != nullptr; + return viewer_node_; } const QString &RenderBackend::cache_id() const @@ -387,9 +323,23 @@ const QString &RenderBackend::cache_id() const return cache_id_; } -void RenderBackend::QueueValueUpdate() +void RenderBackend::QueueValueUpdate(NodeInput* from) { - input_update_queued_ = true; + if (!input_update_queued_.isEmpty()) { + // Remove any inputs that are dependents of this input since they may have been removed since + // it was queued + QList deps = from->GetDependencies(); + + for (int i=0;iparentNode())) { + // We don't need to queue this value since this input supersedes it + input_update_queued_.removeAt(i); + i--; + } + } + } + + input_update_queued_.append(from); } bool RenderBackend::WorkerIsBusy(RenderWorker *worker) const @@ -402,6 +352,87 @@ void RenderBackend::SetWorkerBusyState(RenderWorker *worker, bool busy) processor_busy_state_.replace(processors_.indexOf(worker), busy); } +void RenderBackend::CopyNodeInputValue(NodeInput *input) +{ + // Find our copy of this parameter + Node* our_copy_node = node_copy_map_.value(input->parentNode()); + NodeInput* our_copy = our_copy_node->GetInputWithID(input->id()); + + // Copy the standard/keyframe values between these two inputs + NodeInput::CopyValues(input, + our_copy, + false); + + // Handle connections + if (input->IsConnected() || our_copy->IsConnected()) { + // If one of the inputs is connected, it's likely this change came from connecting or + // disconnecting whatever was connected to it + + { + // We start by removing all old dependencies from the map + QList old_deps = our_copy->GetExclusiveDependencies(); + + foreach (Node* i, old_deps) { + Node* n = node_copy_map_.take(node_copy_map_.key(i)); + copied_graph_.TakeNode(n); + delete n; + } + } + + // Then we copy all node dependencies and connections (if there are any) + CopyNodeMakeConnection(input, our_copy); + } + + // Call on sub-elements too + if (input->IsArray()) { + foreach (NodeInput* i, static_cast(input)->sub_params()) { + CopyNodeInputValue(i); + } + } +} + +Node* RenderBackend::CopyNodeConnections(Node* src_node) +{ + // Check if this node is already in the map + Node* dst_node = 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); + } + + // Make sure its values are copied + Node::CopyInputs(src_node, dst_node, false); + + // Copy all connections + QList src_node_inputs = src_node->GetInputsIncludingArrays(); + QList dst_node_inputs = dst_node->GetInputsIncludingArrays(); + + for (int i=0;iid() << "from" << src_input->parentNode()->id(); + + if (src_input->IsConnected()) { + Node* dst_node = CopyNodeConnections(src_input->get_connected_node()); + + NodeOutput* corresponding_output = dst_node->GetOutputWithID(src_input->get_connected_output()->id()); + + NodeParam::ConnectEdge(corresponding_output, + dst_input); + } +} + bool RenderBackend::AllProcessorsAreAvailable() const { foreach (bool busy, processor_busy_state_) { @@ -455,11 +486,6 @@ void RenderBackend::InitWorkers() processor_busy_state_.fill(false); } -void RenderBackend::QueueRecompile() -{ - recompile_queued_ = true; -} - void RenderBackend::FootageUnavailable(StreamPtr stream, Decoder::RetrieveState state, const TimeRange &range, const rational &stream_time) { if (state == Decoder::kFailedToOpen){ @@ -484,7 +510,7 @@ void RenderBackend::FootageUnavailable(StreamPtr stream, Decoder::RetrieveState || (stream->type() == Stream::kAudio && std::static_pointer_cast(stream)->index_done())) { // Index JUST finished, requeue this time - InvalidateCache(range); + InvalidateCache(range, nullptr); } else { @@ -529,7 +555,7 @@ void RenderBackend::IndexUpdated(Stream* stream) } if (footage_ready) { - InvalidateCache(info.affected_range); + InvalidateCache(info.affected_range, nullptr); footage_wait_info_.removeAt(i); i--; } diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index 7b1b120fb..1e8db284a 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -52,11 +52,7 @@ public: void CancelQueue(); public slots: - void InvalidateCache(const TimeRange &range); - - bool Compile(); - - void Decompile(); + void InvalidateCache(const TimeRange &range, NodeInput *from); signals: void QueueComplete(); @@ -68,10 +64,6 @@ protected: virtual void CloseInternal(); - virtual bool CompileInternal() = 0; - - virtual void DecompileInternal() = 0; - virtual bool CanRender(); virtual TimeRange PopNextFrameFromQueue(); @@ -111,7 +103,7 @@ protected: const QString& cache_id() const; - void QueueValueUpdate(); + void QueueValueUpdate(NodeInput *from); bool AllProcessorsAreAvailable() const; bool WorkerIsBusy(RenderWorker* worker) const; @@ -121,18 +113,17 @@ protected: QVector processors_; - bool compiled_; - QHash render_job_info_; - QList source_node_list_; + QHash node_copy_map_; NodeGraph copied_graph_; -protected slots: - void QueueRecompile(); - private: + void CopyNodeInputValue(NodeInput* input); + Node *CopyNodeConnections(Node *src_node); + void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input); + /** * @brief Internal list of RenderProcessThreads */ @@ -160,8 +151,7 @@ private: QString cache_id_; - bool recompile_queued_; - bool input_update_queued_; + QList input_update_queued_; QVector processor_busy_state_; diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 7e13d1af7..7452b8ab7 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -49,14 +49,12 @@ VideoRenderBackend::VideoRenderBackend(QObject *parent) : void VideoRenderBackend::ConnectViewer(ViewerOutput *node) { connect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache); - connect(node, &ViewerOutput::VideoGraphChanged, this, &VideoRenderBackend::QueueRecompile); connect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength); } void VideoRenderBackend::DisconnectViewer(ViewerOutput *node) { disconnect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache); - disconnect(node, &ViewerOutput::VideoGraphChanged, this, &VideoRenderBackend::QueueRecompile); disconnect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength); frame_cache_.Clear(); diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index ec1a4ad47..5dabdc5cb 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -261,7 +261,10 @@ void VideoRenderWorker::SetFrameGenerationParams(int width, int height, const QM bool VideoRenderWorker::InitInternal() { - ResizeDownloadBuffer(); + if (video_params_.is_valid()) { + ResizeDownloadBuffer(); + } + return true; } diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index 300fbf4b3..2baac4e4c 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -174,7 +174,7 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) // frame gets rendered in this time input_parent->blockSignals(false); - input_parent->parentNode()->InvalidateVisible(input_parent); + input_parent->parentNode()->InvalidateVisible(input_parent, input_parent); } } } @@ -377,7 +377,7 @@ void KeyframeViewBase::ProcessBezierDrag(QPointF mouse_diff_scaled, bool include input_parent->blockSignals(false); - input_parent->parentNode()->InvalidateVisible(input_parent); + input_parent->parentNode()->InvalidateVisible(input_parent, input_parent); } } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index bc90a50e0..aaf1fefba 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -255,7 +255,7 @@ void NodeParamViewWidgetBridge::ProcessSlider(SliderBase *slider, const QVariant input_->blockSignals(false); - input_->parentNode()->InvalidateVisible(input_); + input_->parentNode()->InvalidateVisible(input_, input_); } else { if (dragging_) { @@ -513,9 +513,9 @@ rational NodeParamViewWidgetBridge::GetCurrentTimeAsNodeTime() const return GetAdjustedTime(GetTimeTarget(), input_->parentNode(), time_, NodeParam::kInput); } -void NodeParamViewWidgetBridge::InputValueChanged(const rational &start, const rational &end) +void NodeParamViewWidgetBridge::InputValueChanged(const TimeRange &range) { - if (!dragging_ && start <= time_ && end >= time_) { + if (!dragging_ && range.in() <= time_ && range.out() >= time_) { // We'll need to update the widgets because the values have changed on our current time UpdateWidgetValues(); } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h index 51b4c6ebc..d1543c73e 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h @@ -68,7 +68,7 @@ private: private slots: void WidgetCallback(); - void InputValueChanged(const rational& start, const rational& end); + void InputValueChanged(const TimeRange& range); void PropertyChanged(const QString& key, const QVariant& value); diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index 8dd71617a..8ba10b15e 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -274,7 +274,9 @@ void TrackRippleRemoveAreaCommand::redo_internal() track_->UnblockInvalidateCache(); - track_->InvalidateCache(in_, insert_ ? out_ : RATIONAL_MAX); + track_->InvalidateCache(TimeRange(in_, insert_ ? out_ : RATIONAL_MAX), + track_->block_input(), + track_->block_input()); } void TrackRippleRemoveAreaCommand::undo_internal() @@ -328,7 +330,7 @@ void TrackRippleRemoveAreaCommand::undo_internal() track_->UnblockInvalidateCache(); - track_->InvalidateCache(in_, insert_ ? out_ : RATIONAL_MAX); + track_->InvalidateCache(TimeRange(in_, insert_ ? out_ : RATIONAL_MAX), track_->block_input(), track_->block_input()); } TrackPlaceBlockCommand::TrackPlaceBlockCommand(TrackList *timeline, int track, Block *block, rational in, QUndoCommand *parent) : diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index f25e1084b..4107ceab1 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -152,8 +152,8 @@ 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::VideoGraphChanged, this, &ViewerWidget::UpdateStack); - connect(n, &ViewerOutput::AudioGraphChanged, this, &ViewerWidget::UpdateStack); + connect(n, &ViewerOutput::VideoChangedBetween, this, &ViewerWidget::UpdateStack); + connect(n, &ViewerOutput::AudioChangedBetween, this, &ViewerWidget::UpdateStack); SizeChangedSlot(n->video_params().width(), n->video_params().height()); LengthChangedSlot(n->Length()); @@ -194,8 +194,8 @@ 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::VideoGraphChanged, this, &ViewerWidget::UpdateStack); - disconnect(n, &ViewerOutput::AudioGraphChanged, this, &ViewerWidget::UpdateStack); + disconnect(n, &ViewerOutput::VideoChangedBetween, this, &ViewerWidget::UpdateStack); + disconnect(n, &ViewerOutput::AudioChangedBetween, this, &ViewerWidget::UpdateStack); // Effectively disables the viewer and clears the state SizeChangedSlot(0, 0); @@ -434,10 +434,14 @@ void ViewerWidget::SetColorTransform(const ColorTransform &transform, ViewerDisp void ViewerWidget::UpdateStack() { - if (!GetConnectedNode() || GetConnectedNode()->texture_input()->IsConnected()) { - stack_->setCurrentWidget(sizer_); - } else { + if (GetConnectedNode() + && !GetConnectedNode()->texture_input()->IsConnected() + && GetConnectedNode()->samples_input()->IsConnected()) { + // If we have a node AND video is disconnected AND audio is connected, show waveform view stack_->setCurrentWidget(waveform_view_); + } else { + // Otherwise show regular display + stack_->setCurrentWidget(sizer_); } } @@ -527,7 +531,7 @@ void ViewerWidget::UpdateRendererParameters() if (video_renderer_->params() != vparam) { video_renderer_->SetParameters(vparam); - video_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length())); + video_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length()), nullptr); } AudioRenderingParams aparam(GetConnectedNode()->audio_params(), @@ -535,7 +539,7 @@ void ViewerWidget::UpdateRendererParameters() if (audio_renderer_->params() != aparam) { audio_renderer_->SetParameters(aparam); - audio_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length())); + audio_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length()), nullptr); } } @@ -869,9 +873,9 @@ void ViewerWidget::SetZoomFromMenu(QAction *action) sizer_->SetZoom(action->data().toInt()); } -void ViewerWidget::InvalidateVisible() +void ViewerWidget::InvalidateVisible(NodeInput* source) { - video_renderer_->InvalidateCache(TimeRange(GetTime(), GetTime())); + video_renderer_->InvalidateCache(TimeRange(GetTime(), GetTime()), source); } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index ec638238a..8cf6bb24c 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -233,7 +233,7 @@ private slots: void SetZoomFromMenu(QAction* action); - void InvalidateVisible(); + void InvalidateVisible(NodeInput *source); void UpdateStack();