diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index e4ec160a1..21f30578e 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -21,6 +21,11 @@ const rational &TimeRange::out() const return out_; } +const rational &TimeRange::length() const +{ + return length_; +} + void TimeRange::set_in(const rational &in) { in_ = in; @@ -40,6 +45,36 @@ void TimeRange::set_range(const rational &in, const rational &out) normalize(); } +bool TimeRange::operator<(const TimeRange &r) const +{ + return length() < r.length(); +} + +bool TimeRange::operator<=(const TimeRange &r) const +{ + return length() <= r.length(); +} + +bool TimeRange::operator>(const TimeRange &r) const +{ + return length() > r.length(); +} + +bool TimeRange::operator>=(const TimeRange &r) const +{ + return length() >= r.length(); +} + +bool TimeRange::operator==(const TimeRange &r) const +{ + return length() == r.length(); +} + +bool TimeRange::operator!=(const TimeRange &r) const +{ + return length() != r.length(); +} + void TimeRange::normalize() { // If `out` is earlier than `in`, swap them @@ -48,4 +83,7 @@ void TimeRange::normalize() in_ = out_; out_ = temp; } + + // Calculate length + length_ = out_ - in_; } diff --git a/app/common/timerange.h b/app/common/timerange.h index 1ff31992b..7ca30956f 100644 --- a/app/common/timerange.h +++ b/app/common/timerange.h @@ -10,16 +10,25 @@ public: const rational& in() const; const rational& out() const; + const rational& length() const; void set_in(const rational& in); void set_out(const rational& out); void set_range(const rational& in, const rational& out); + bool operator<(const TimeRange &r) const; + bool operator<=(const TimeRange &r) const; + bool operator>(const TimeRange &r) const; + bool operator>=(const TimeRange &r) const; + bool operator==(const TimeRange &r) const; + bool operator!=(const TimeRange &r) const; + private: void normalize(); rational in_; rational out_; + rational length_; }; #endif // TIMERANGE_H diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 9586a9e33..023359d0c 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -64,11 +64,11 @@ void Block::set_length(const rational &length) { Q_ASSERT(length > 0); - Lock(); + LockUserInput(); length_ = length; - Unlock(); + UnlockUserInput(); Refresh(); } @@ -84,7 +84,7 @@ void Block::set_length_and_media_in(const rational &length) Block *Block::previous() { - return ValueToPtr(previous_input_->get_value(0)); + return ValueToPtr(previous_input_->get_realtime_value_of_connected_output()); } Block *Block::next() @@ -97,11 +97,8 @@ NodeInput *Block::previous_input() return previous_input_; } -QVariant Block::Value(NodeOutput *output, const rational &in, const rational &out) +QVariant Block::Value(NodeOutput *output) { - Q_UNUSED(in) - Q_UNUSED(out) - if (output == block_output_) { // Simply set the output value to a pointer to this Block return PtrToValue(this); @@ -179,7 +176,7 @@ const rational &Block::media_in() void Block::set_media_in(const rational &media_in) { - Lock(); + LockUserInput(); if (media_in_ != media_in) { media_in_ = media_in; @@ -188,7 +185,7 @@ void Block::set_media_in(const rational &media_in) SendInvalidateCache(in(), out()); } - Unlock(); + UnlockUserInput(); } const QString &Block::block_name() diff --git a/app/node/block/block.h b/app/node/block/block.h index 9facd3593..5e0a2d027 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -80,6 +80,8 @@ public: virtual bool IsBlock() override; + virtual QVariant Value(NodeOutput* output) override; + public slots: /** * @brief Refreshes internal cache of in/out points up to date @@ -105,8 +107,6 @@ signals: void Refreshed(); protected: - virtual QVariant Value(NodeOutput* output, const rational &in, const rational &out) override; - rational SequenceToMediaTime(const rational& sequence_time); rational MediaToSequenceTime(const rational& media_time); diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 3f5ed6aa3..b8560a49e 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -61,21 +61,6 @@ NodeInput *ClipBlock::texture_input() return texture_input_; } -QVariant ClipBlock::Value(NodeOutput* param, const rational& v_in, const rational &v_out) -{ - if (param == buffer_output()) { - // If the time retrieved is within this block, get texture information - if (texture_input()->IsConnected() && v_in >= in() && v_out < out()) { - // Retrieve texture - // We convert the time given (timeline time) to media time - return texture_input_->get_value(SequenceToMediaTime(v_in), SequenceToMediaTime(v_out)); - } - return 0; - } - - return Block::Value(param, v_in, v_out); -} - void ClipBlock::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from) { // If signal is from texture input, transform all times from media time to sequence time @@ -104,3 +89,12 @@ QList ClipBlock::RunDependencies(NodeOutput *output, const ratio return deps; } + +TimeRange ClipBlock::InputTimeAdjustment(NodeInput *input, const TimeRange &input_time) +{ + if (input == texture_input_) { + return TimeRange(SequenceToMediaTime(input_time.in()), SequenceToMediaTime(input_time.out())); + } + + return Block::InputTimeAdjustment(input, input_time); +} diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index af16904c5..072d9beda 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -46,8 +46,7 @@ public: virtual QList RunDependencies(NodeOutput *output, const rational &time) override; -protected: - virtual QVariant Value(NodeOutput* output, const rational &v_in, const rational &v_out) override; + virtual TimeRange InputTimeAdjustment(NodeInput* input, const TimeRange& input_time) override; private: NodeInput* texture_input_; diff --git a/app/node/color/opacity/opacity.cpp b/app/node/color/opacity/opacity.cpp index 5fce51451..0989defae 100644 --- a/app/node/color/opacity/opacity.cpp +++ b/app/node/color/opacity/opacity.cpp @@ -24,7 +24,7 @@ OpacityNode::OpacityNode() { opacity_input_ = new NodeInput("opacity_in"); opacity_input_->set_data_type(NodeParam::kFloat); - opacity_input_->set_value(100); + opacity_input_->set_value_at_time(0, 100); opacity_input_->set_minimum(0); opacity_input_->set_maximum(100); AddParameter(opacity_input_); diff --git a/app/node/distort/transform/transform.cpp b/app/node/distort/transform/transform.cpp index 8fb4c8e40..aa7925547 100644 --- a/app/node/distort/transform/transform.cpp +++ b/app/node/distort/transform/transform.cpp @@ -35,7 +35,7 @@ TransformDistort::TransformDistort() scale_input_ = new NodeInput("scale_in"); scale_input_->set_data_type(NodeParam::kVec2); - scale_input_->set_value(QVector2D(100.0f, 100.0f)); + scale_input_->set_value_at_time(0, QVector2D(100.0f, 100.0f)); AddParameter(scale_input_); anchor_input_ = new NodeInput("anchor_in"); @@ -79,25 +79,23 @@ void TransformDistort::Retranslate() anchor_input_->set_name(tr("Anchor Point")); } -QVariant TransformDistort::Value(NodeOutput *output, const rational &in, const rational &out) +QVariant TransformDistort::Value(NodeOutput *output) { - Q_UNUSED(out) - if (output == matrix_output_) { QMatrix4x4 mat; // Position translate - QVector2D pos = position_input_->get_value(in).value(); + QVector2D pos = position_input_->value().value(); mat.translate(pos); // Rotation - mat.rotate(rotation_input_->get_value(in).toFloat(), 0, 0, 1); + mat.rotate(rotation_input_->value().toFloat(), 0, 0, 1); // Scale - mat.scale(scale_input_->get_value(in).value()*0.01f); + mat.scale(scale_input_->value().value()*0.01f); // Anchor Point - mat.translate(-anchor_input_->get_value(in).value()); + mat.translate(-anchor_input_->value().value()); return mat; } diff --git a/app/node/distort/transform/transform.h b/app/node/distort/transform/transform.h index 4647cf39c..8a9268158 100644 --- a/app/node/distort/transform/transform.h +++ b/app/node/distort/transform/transform.h @@ -38,8 +38,7 @@ public: virtual void Retranslate() override; -protected: - virtual QVariant Value(NodeOutput *output, const rational &in, const rational &out) override; + virtual QVariant Value(NodeOutput *output) override; private: NodeInput* position_input_; diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index 94e92eb61..c5383edd7 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -56,24 +56,18 @@ NodeOutput *SolidGenerator::texture_output() return texture_output_; } -QVariant SolidGenerator::Value(NodeOutput *output, const rational &in, const rational &out) +QString SolidGenerator::Code(NodeOutput *output) { - Q_UNUSED(output) - Q_UNUSED(in) - Q_UNUSED(out) - - /* - // FIXME: Test code - if (texture_ == nullptr) { - QImage img(1920, 1080, QImage::Format_RGBA8888_Premultiplied); - img.fill(Qt::red); - - texture_ = new QOpenGLTexture(img); + if (output == texture_output()) { + // FIXME: Not color managed + return "#version 110\n" + "\n" + "uniform vec4 color_in;\n" + "\n" + "void main(void) {\n" + " gl_FragColor = color_in;\n" + "}\n"; } - texture_output_->set_value(texture_->textureId()); - // End test code - */ - - return 0; + return Node::Code(output); } diff --git a/app/node/generator/solid/solid.h b/app/node/generator/solid/solid.h index 7b70885b4..4c0cbf49a 100644 --- a/app/node/generator/solid/solid.h +++ b/app/node/generator/solid/solid.h @@ -41,8 +41,7 @@ public: NodeOutput* texture_output(); -protected: - virtual QVariant Value(NodeOutput* output, const rational &in, const rational &out) override; + virtual QString Code(NodeOutput* output) override; private: NodeInput* color_input_; diff --git a/app/node/input.cpp b/app/node/input.cpp index b195f29de..d43a986d7 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -20,6 +20,7 @@ #include "input.h" +#include "common/lerp.h" #include "node.h" #include "output.h" @@ -78,54 +79,161 @@ Node *NodeInput::get_connected_node() return nullptr; } -QVariant NodeInput::get_value(const rational& in, const rational& out) +QVariant NodeInput::get_value_at_time(const rational &time) { - QVariant v; - - if (in_ != in || out_ != out || !value_caching_) { - // Retrieve the value - if (!edges_.isEmpty()) { - // A connection - use the output of the connected Node - value_ = get_connected_output()->get_value(in, out); - } else { - // No connections - use the internal value - // FIXME: Re-implement keyframing - value_ = keyframes_.first().value(); + if (is_keyframing()) { + if (keyframes_.first().time() >= time) { + // This time precedes any keyframe, so we just return the first value + return keyframes_.first().value(); } - in_ = in; - out_ = out; + if (keyframes_.last().time() <= time) { + // This time is after any keyframes so we return the last value + return keyframes_.last().value(); + } + + // If we're here, the time must be somewhere in between the keyframes + for (int i=0;i time) { + // We must interpolate between these keyframes + + if (before.type() == NodeKeyframe::kBezier && after.type() == NodeKeyframe::kBezier) { + // FIXME: Perform a cubic bezier interpolation + } else if (before.type() == NodeKeyframe::kLinear && after.type() == NodeKeyframe::kBezier) { + // FIXME: Perform a quadratic bezier interpolation with anchors from the AFTER keyframe + } else if (before.type() == NodeKeyframe::kLinear && after.type() == NodeKeyframe::kBezier) { + // FIXME: Perform a quadratic bezier interpolation with anchors from the BEFORE keyframe + } else { + // To have arrived here, the keyframes must both be linear + qreal period_progress = (time.toDouble() - before.time().toDouble()) / (after.time().toDouble() - before.time().toDouble()); + qreal interpolated_value = lerp(before.value().toDouble(), after.value().toDouble(), period_progress); + + return interpolated_value; + } + } + } } - v = value_; - - return v; + return keyframes_.first().value(); } -void NodeInput::set_value(const QVariant &value) +void NodeInput::set_value_at_time(const rational &time, const QVariant &value) { - bool lock_mutex = (parent() != nullptr); + if (parent() != nullptr) + parent()->LockUserInput(); - if (lock_mutex) parent()->Lock(); + if (is_keyframing()) { + // Insert value into the keyframe list chronologically + + if (keyframes_.first().time() > time) { + + // Store this away for the ValueChanged signal we emit later + rational existing_first_key = keyframes_.first().time(); + + // Insert at the beginning + keyframes_.prepend(NodeKeyframe(time, value, keyframes_.first().type())); + + // Value has changed since the earliest point up until the ex-first keyframe (since the frames + // interpolating between the key we're adding and the key that existed are changing too) + emit ValueChanged(RATIONAL_MIN, existing_first_key); + + } else if (keyframes_.first().time() == time) { + + // Replace first value + keyframes_.first().set_value(value); + + // Value has changed since the earliest point up until the keyframe we just changed + emit ValueChanged(RATIONAL_MIN, time); + + } else if (keyframes_.last().time() < time) { + + // Store this away for the ValueChanged signal we emit later + rational existing_last_key = keyframes_.last().time(); + + // Append at the end + keyframes_.append(NodeKeyframe(time, value, keyframes_.last().type())); + + // Value has changed since the ex-last point up until the latest possible point (since the frames + // interpolating between the key we're adding and the key that existed are changing too) + emit ValueChanged(existing_last_key, RATIONAL_MAX); + + } else if (keyframes_.last().time() == time) { + + // Replace last value + keyframes_.last().set_value(value); + + // Value has changed from this point until the latest possible point + emit ValueChanged(time, RATIONAL_MAX); + + } else { + for (int i=0;i time) { + // Insert value in between these two keyframes + keyframes_.insert(i+1, NodeKeyframe(time, value, before.type())); + + // Values have changed since the last keyframe and the next one + emit ValueChanged(before.time(), after.time()); + break; + } + } + } - if (keyframing()) { - // FIXME: Keyframing code using time() } else { - // Not keyframing, so invalidate entire time length keyframes_.first().set_value(value); + // Values have changed for all times since the value is static emit ValueChanged(RATIONAL_MIN, RATIONAL_MAX); } - if (lock_mutex) parent()->Unlock(); + if (parent() != nullptr) + parent()->UnlockUserInput(); } -bool NodeInput::keyframing() +const QVariant &NodeInput::value() +{ + return stored_value_; +} + +void NodeInput::set_stored_value(const QVariant &value) +{ + stored_value_ = value; +} + +QVariant NodeInput::get_realtime_value_of_connected_output() +{ + if (get_connected_output() == nullptr) { + return 0; + } + + return get_connected_output()->get_realtime_value(); +} + +bool NodeInput::is_keyframing() { return keyframing_; } -void NodeInput::set_keyframing(bool k) +void NodeInput::set_is_keyframing(bool k) { keyframing_ = k; } @@ -178,7 +286,7 @@ void NodeInput::CopyValues(NodeInput *source, NodeInput *dest) dest->keyframes_ = source->keyframes_; // Copy keyframing state - dest->set_keyframing(source->keyframing()); + dest->set_is_keyframing(source->is_keyframing()); // Copy connections if (source->get_connected_output() != nullptr) { diff --git a/app/node/input.h b/app/node/input.h index cfe1607ed..5a9029ed2 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -77,35 +77,36 @@ public: Node* get_connected_node(); /** - * @brief Get the value at a given time - * - * This function will automatically retrieve the correct value for this input at the given time. - * - * If an output is connected to this input, a request is made to that output for its value at this time. If multiple - * outputs are connected (\see can_accept_multiple_inputs()), a QList (casted to a QVariant) is returned - * instead, listing all the outputs' values currently connected. - * - * If no output is connected, this will return a user-defined value, either a static value if this input is not - * keyframed, or an interpolated value between the keyframes at this time. + * @brief Get currently stored value */ - QVariant get_value(const rational &in, const rational &out = -1); + const QVariant& value(); + + /** + * @brief Calculate what the stored value should be at a certain time + */ + QVariant get_value_at_time(const rational& time); + + /** + * @brief Sets what value should be seen at a specific time + */ + void set_value_at_time(const rational& time, const QVariant& value); /** * @brief Set the value at a given time - * - * This function will only work if there are no outputs connected. */ - void set_value(const QVariant& value); + void set_stored_value(const QVariant& value); + + QVariant get_realtime_value_of_connected_output(); /** * @brief Return whether keyframing is enabled on this input or not */ - bool keyframing(); + bool is_keyframing(); /** * @brief Set whether keyframing is enabled on this input or not */ - void set_keyframing(bool k); + void set_is_keyframing(bool k); /** * @brief Return whether the Node is dependent on this input or not @@ -181,6 +182,11 @@ private: */ QVariant maximum_; + /** + * @brief Internal stored value + */ + QVariant stored_value_; + }; #endif // NODEINPUT_H diff --git a/app/node/input/media/audio/audio.cpp b/app/node/input/media/audio/audio.cpp index 42d1d16a2..f3fde6228 100644 --- a/app/node/input/media/audio/audio.cpp +++ b/app/node/input/media/audio/audio.cpp @@ -26,9 +26,9 @@ QString AudioInput::Description() return tr("Import an audio footage stream."); } -QVariant AudioInput::Value(NodeOutput *output, const rational &in, const rational &out) +QVariant AudioInput::Value(NodeOutput *) { - if (output == samples_output_) { + /*if (output == samples_output_) { // Make sure decoder is set up if (!SetupDecoder()) { return 0; @@ -41,7 +41,7 @@ QVariant AudioInput::Value(NodeOutput *output, const rational &in, const rationa samples.resize(frame_->audio_params().samples_to_bytes(frame_->sample_count())); memcpy(samples.data(), frame_->data(), static_cast(samples.size())); return samples; - } + }*/ return 0; } diff --git a/app/node/input/media/audio/audio.h b/app/node/input/media/audio/audio.h index 1faa55d9d..5ddc957a2 100644 --- a/app/node/input/media/audio/audio.h +++ b/app/node/input/media/audio/audio.h @@ -14,7 +14,7 @@ public: virtual QString Description() override; protected: - virtual QVariant Value(NodeOutput* output, const rational& in, const rational& out) override; + virtual QVariant Value(NodeOutput* output) override; private: NodeOutput* samples_output_; diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index 7e0bbaf0d..33f743787 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -37,12 +37,12 @@ void MediaInput::Release() StreamPtr MediaInput::footage() { - return footage_input_->get_value(0).value(); + return footage_input_->get_value_at_time(0).value(); } void MediaInput::SetFootage(StreamPtr f) { - footage_input_->set_value(QVariant::fromValue(f)); + footage_input_->set_value_at_time(0, QVariant::fromValue(f)); } bool MediaInput::SetupDecoder() diff --git a/app/node/input/media/video/video.cpp b/app/node/input/media/video/video.cpp index d86fa39c7..07603db1e 100644 --- a/app/node/input/media/video/video.cpp +++ b/app/node/input/media/video/video.cpp @@ -15,7 +15,6 @@ VideoInput::VideoInput() AddParameter(matrix_input_); texture_output_ = new NodeOutput("tex_out"); - texture_output_->SetValueCachingEnabled(false); AddParameter(texture_output_); } diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index 2ce4ef695..69421a7e4 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -28,7 +28,14 @@ NodeKeyframe::NodeKeyframe() : } -const rational &NodeKeyframe::time() +NodeKeyframe::NodeKeyframe(const rational &time, const QVariant &value, const NodeKeyframe::Type &type) : + time_(time), + value_(value), + type_(type) +{ +} + +const rational &NodeKeyframe::time() const { return time_; } @@ -38,7 +45,7 @@ void NodeKeyframe::set_time(const rational &time) time_ = time; } -const QVariant &NodeKeyframe::value() +const QVariant &NodeKeyframe::value() const { return value_; } @@ -48,7 +55,7 @@ void NodeKeyframe::set_value(const QVariant &value) value_ = value; } -const NodeKeyframe::Type &NodeKeyframe::type() +const NodeKeyframe::Type &NodeKeyframe::type() const { return type_; } diff --git a/app/node/keyframe.h b/app/node/keyframe.h index 59dbe6be2..1e3e8d56c 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -41,27 +41,34 @@ public: kBezier }; + static const Type kDefaultType = kLinear; + /** * @brief NodeKeyframe Constructor */ NodeKeyframe(); + /** + * @brief NodeKeyframe Constructor + */ + NodeKeyframe(const rational& time, const QVariant& value, const Type& type); + /** * @brief The time this keyframe is set at */ - const rational& time(); + const rational& time() const; void set_time(const rational& time); /** * @brief The value of this keyframe (i.e. the value to use at this keyframe's time) */ - const QVariant& value(); + const QVariant& value() const; void set_value(const QVariant &value); /** * @brief The method of interpolation to use with this keyframe */ - const Type& type(); + const Type& type() const; void set_type(const Type& type); private: diff --git a/app/node/node.cpp b/app/node/node.cpp index 81e121026..53b76f2ae 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -72,11 +72,9 @@ void Node::RemoveParameter(NodeParam *param) delete param; } -QVariant Node::Value(NodeOutput *output, const rational &in, const rational &out) +QVariant Node::Value(NodeOutput *output) { Q_UNUSED(output) - Q_UNUSED(in) - Q_UNUSED(out) return QVariant(); } @@ -85,11 +83,18 @@ void Node::InvalidateCache(const rational &start_range, const rational &end_rang { Q_UNUSED(from) - ClearCachedValuesInParameters(start_range, end_range); - SendInvalidateCache(start_range, end_range); } +TimeRange Node::InputTimeAdjustment(NodeInput *input, const TimeRange &input_time) +{ + Q_UNUSED(input) + + // Default behavior is no time adjustment at all + + return input_time; +} + void Node::SendInvalidateCache(const rational &start_range, const rational &end_range) { // Loop through all parameters (there should be no children that are not NodeParams) @@ -110,14 +115,24 @@ void Node::SendInvalidateCache(const rational &start_range, const rational &end_ } } -void Node::Lock() +void Node::LockUserInput() { - lock_.lock(); + user_input_lock_.lock(); } -void Node::Unlock() +void Node::UnlockUserInput() { - lock_.unlock(); + user_input_lock_.unlock(); +} + +void Node::LockProcessing() +{ + processing_lock_.lock(); +} + +void Node::UnlockProcessing() +{ + processing_lock_.unlock(); } void Node::CopyInputs(Node *source, Node *destination) @@ -159,11 +174,11 @@ rational Node::LastProcessedTime() { rational t; - Lock(); + LockUserInput(); t = last_processed_time_; - Unlock(); + UnlockUserInput(); return t; } @@ -172,39 +187,15 @@ NodeOutput *Node::LastProcessedOutput() { NodeOutput* o; - Lock(); + LockUserInput(); o = last_processed_parameter_; - Unlock(); + UnlockUserInput(); return o; } -void Node::ClearCachedValuesInParameters(const rational &start_range, const rational &end_range) -{ - Q_UNUSED(start_range) - Q_UNUSED(end_range) - - // Loop through all parameters and clear cached values - foreach (NodeParam* param, params_) { - //if (param->LastRequestedTime() >= start_range && param->LastRequestedTime() <= end_range) { - param->ClearCachedValue(); - //} - } -} - -QVariant Node::Run(NodeOutput* output, const rational& in, const rational &out) -{ - run_lock_.lock(); - - QVariant v = Value(output, in, out); - - run_lock_.unlock(); - - return v; -} - const QList& Node::parameters() { return params_; @@ -367,7 +358,7 @@ void Node::DisconnectAll() } } -void Node::Hash(QCryptographicHash *hash, NodeOutput* from, const rational &time) +/*void Node::Hash(QCryptographicHash *hash, NodeOutput* from, const rational &time) { // Add this Node's ID hash->addData(id().toUtf8()); @@ -380,7 +371,7 @@ void Node::Hash(QCryptographicHash *hash, NodeOutput* from, const rational &time // Get the value at this time NodeInput* input = static_cast(param); - QVariant v = input->get_value(time); + QVariant v = input->value(time); hash->addData(NodeParam::ValueToBytes(input->data_type(), v)); } @@ -392,7 +383,7 @@ void Node::Hash(QCryptographicHash *hash, NodeOutput* from, const rational &time // Hash the connected node dep.node()->parent()->Hash(hash, dep.node(), dep.in()); } -} +}*/ QVariant Node::PtrToValue(void *ptr) { diff --git a/app/node/node.h b/app/node/node.h index 6aed68c46..6fed53e7d 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -129,13 +129,6 @@ public: */ virtual QString Code(NodeOutput* output); - /** - * @brief Wrapper for Process() - * - * It's recommended to call this directly over Value(), yet in derivatives of Node, override Value(). - */ - QVariant Run(NodeOutput* output, const rational &in, const rational &out); - /** * @brief For nodes that have different dependencies at different times, this function can be used for that purpose * @@ -176,7 +169,7 @@ public: /** * @brief Add's unique information about this Node at the given time to a QCryptographicHash */ - virtual void Hash(QCryptographicHash* hash, NodeOutput *from, const rational& time); + //virtual void Hash(QCryptographicHash* hash, NodeOutput *from, const rational& time); /** * @brief Convert a pointer to a value that can be sent between NodeParams @@ -200,15 +193,19 @@ public: */ virtual void InvalidateCache(const rational& start_range, const rational& end_range, NodeInput* from = nullptr); - /** - * @brief Lock mutex (for thread safety) - */ - void Lock(); + virtual TimeRange InputTimeAdjustment(NodeInput* input, const TimeRange& input_time); /** - * @brief Unock mutex (for thread safety) + * @brief User input lock prevents any user changes while a graph is being rendered */ - void Unlock(); + void LockUserInput(); + void UnlockUserInput(); + + /** + * @brief Processing lock prevents more than one thread trying to process a Node at once + */ + void LockProcessing(); + void UnlockProcessing(); /** * @brief Copies inputs from from Node to another including connections @@ -235,6 +232,21 @@ public: */ virtual bool IsBlock(); + /** + * @brief The main processing function + * + * The node's main purpose is to take values from inputs to set values in outputs. For whatever subclass node you + * create, this is where the code for that goes. + * + * Note that as a video editor, the node graph has to work across time. Depending on the purpose of your node, it may + * output different values depending on the time, and even if not, it will likely be receiving different input + * depending on the time. Most of the difficult work here is handled by NodeInput::get_value() which you should pass + * the `time` parameter to. It will return its value (at that time, if it's keyframed), or pass the time to a + * corresponding output if it's connected to one. If your node doesn't directly deal with time, the default behavior + * of the NodeParam objects will handle everything related to it automatically. + */ + virtual QVariant Value(NodeOutput* output); + protected: /** * @brief Add a parameter to this node @@ -252,21 +264,6 @@ protected: */ void RemoveParameter(NodeParam* param); - /** - * @brief The main processing function - * - * The node's main purpose is to take values from inputs to set values in outputs. For whatever subclass node you - * create, this is where the code for that goes. - * - * Note that as a video editor, the node graph has to work across time. Depending on the purpose of your node, it may - * output different values depending on the time, and even if not, it will likely be receiving different input - * depending on the time. Most of the difficult work here is handled by NodeInput::get_value() which you should pass - * the `time` parameter to. It will return its value (at that time, if it's keyframed), or pass the time to a - * corresponding output if it's connected to one. If your node doesn't directly deal with time, the default behavior - * of the NodeParam objects will handle everything related to it automatically. - */ - virtual QVariant Value(NodeOutput* output, const rational &in, const rational &out); - /** * @brief Retrieve the last timecode Process() was called with */ @@ -325,12 +322,12 @@ private: /** * @brief Used for thread safety from main thread */ - QMutex lock_; + QMutex user_input_lock_; /** * @brief Used for thread safety between multiple threads */ - QMutex run_lock_; + QMutex processing_lock_; /** * @brief Internal variable for whether this Node can be deleted or not diff --git a/app/node/output.cpp b/app/node/output.cpp index 785029ced..3ef22a09e 100644 --- a/app/node/output.cpp +++ b/app/node/output.cpp @@ -23,8 +23,7 @@ #include "node/node.h" NodeOutput::NodeOutput(const QString &id) : - NodeParam(id), - linked_input_(nullptr) + NodeParam(id) { } @@ -33,40 +32,33 @@ NodeParam::Type NodeOutput::type() return kOutput; } -QVariant NodeOutput::get_value(const rational& in, const rational& out) +QVariant NodeOutput::get_realtime_value() { - mutex_.lock(); + parent()->LockProcessing(); - QVariant v; + QVariant v = parent()->Value(this); - if (in_ != in || out_ != out || !value_caching_) { - // Update the value - value_ = parent()->Run(this, in, out); - - in_ = in; - out_ = out; - } - - v = value_; - - mutex_.unlock(); + parent()->UnlockProcessing(); return v; } -void NodeOutput::push_value(const QVariant &v, const rational &in, const rational &out) +bool NodeOutput::has_cached_value(const TimeRange &time) { - value_ = v; - in_ = in; - out_ = out; + return cached_values_.contains(time); } -NodeInput *NodeOutput::linked_input() +QVariant NodeOutput::get_cached_value(const TimeRange &time) { - return linked_input_; + return cached_values_.value(time); } -void NodeOutput::set_linked_input(NodeInput *link) +void NodeOutput::cache_value(const TimeRange &time, const QVariant &value) { - linked_input_ = link; + cached_values_.insert(time, value); +} + +void NodeOutput::drop_cached_values() +{ + cached_values_.clear(); } diff --git a/app/node/output.h b/app/node/output.h index 671e5e360..7d6ffa3ee 100644 --- a/app/node/output.h +++ b/app/node/output.h @@ -21,6 +21,7 @@ #ifndef NODEOUTPUT_H #define NODEOUTPUT_H +#include "common/timerange.h" #include "param.h" /** @@ -39,30 +40,15 @@ public: */ virtual Type type() override; - /** - * @brief Get the value of this output at a given tie - * - * This function is intended to primarily be called by any connected NodeInputs. - * - * The first thing this function does is request the parent Node object to Process() at this time. The Node should - * then perform whatever actions necessary (usually taking data from inputs and creating output data) to set the - * correct value that this output should have at this time (the Node should use set_value() for this). This function - * will then return the value that was set after Process() returned. - * - * In many cases for efficiency, the Node can also ignore this request if it knows the output data will not change - * (i.e. if the time has not changed from the last Process()). - */ - virtual QVariant get_value(const rational &in, const rational &out); + QVariant get_realtime_value(); - void push_value(const QVariant& v, const rational& in, const rational &out); - - NodeInput* linked_input(); - void set_linked_input(NodeInput* link); + bool has_cached_value(const TimeRange& time); + QVariant get_cached_value(const TimeRange& time); + void cache_value(const TimeRange& time, const QVariant& value); + void drop_cached_values(); private: - QMutex mutex_; - - NodeInput* linked_input_; + QMap cached_values_; }; diff --git a/app/node/output/timeline/timeline.cpp b/app/node/output/timeline/timeline.cpp index efa492991..33793e202 100644 --- a/app/node/output/timeline/timeline.cpp +++ b/app/node/output/timeline/timeline.cpp @@ -93,11 +93,8 @@ const rational &TimelineOutput::Timebase() return timebase_; } -QVariant TimelineOutput::Value(NodeOutput *output, const rational &in, const rational &out) +QVariant TimelineOutput::Value(NodeOutput *output) { - Q_UNUSED(in) - Q_UNUSED(out) - if (output == length_output_) { return QVariant::fromValue(length_); } diff --git a/app/node/output/timeline/timeline.h b/app/node/output/timeline/timeline.h index a8f8a2268..2ea03c9ad 100644 --- a/app/node/output/timeline/timeline.h +++ b/app/node/output/timeline/timeline.h @@ -67,7 +67,7 @@ signals: void TrackRemoved(TrackOutput* track); protected: - virtual QVariant Value(NodeOutput* output, const rational &in, const rational &out) override; + virtual QVariant Value(NodeOutput* output) override; private: QVector track_inputs_; diff --git a/app/node/output/timeline/tracklist.cpp b/app/node/output/timeline/tracklist.cpp index 7efc3447b..b31be3cf2 100644 --- a/app/node/output/timeline/tracklist.cpp +++ b/app/node/output/timeline/tracklist.cpp @@ -34,7 +34,7 @@ TrackList::TrackList(TimelineOutput* parent, const enum TrackType &type, NodeInp TrackOutput *TrackList::attached_track() { - return Node::ValueToPtr(track_input_->get_value(0)); + return Node::ValueToPtr(track_input_->get_realtime_value_of_connected_output()); } void TrackList::AttachTrack(TrackOutput *track) @@ -176,7 +176,7 @@ void TrackList::TrackConnectionRemoved(NodeEdgePtr edge) return; } - DetachTrack(Node::ValueToPtr(edge->output()->get_value(0, 0))); + DetachTrack(Node::ValueToPtr(edge->output()->get_realtime_value())); } void TrackList::TrackEdgeAdded(NodeEdgePtr edge) @@ -186,7 +186,7 @@ void TrackList::TrackEdgeAdded(NodeEdgePtr edge) // If this edge pertains to the track's track input, all the tracks just added need attaching if (edge->input() == track->track_input()) { - TrackOutput* added_track = Node::ValueToPtr(edge->output()->get_value(0, 0)); + TrackOutput* added_track = Node::ValueToPtr(edge->output()->get_realtime_value()); AttachTrack(added_track); } @@ -199,7 +199,7 @@ void TrackList::TrackEdgeRemoved(NodeEdgePtr edge) // If this edge pertains to the track's track input, all the tracks just added need attaching if (edge->input() == track->track_input()) { - TrackOutput* added_track = Node::ValueToPtr(edge->output()->get_value(0, 0)); + TrackOutput* added_track = Node::ValueToPtr(edge->output()->get_realtime_value()); DetachTrack(added_track); } diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index ce35f3ecb..1ce1bbce1 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -139,7 +139,7 @@ QList TrackOutput::RunDependencies(NodeOutput* output, const rat TrackOutput *TrackOutput::next_track() { - return ValueToPtr(track_input_->get_value(0)); + return ValueToPtr(track_input_->get_realtime_value_of_connected_output()); } NodeInput *TrackOutput::track_input() @@ -191,12 +191,12 @@ void TrackOutput::InvalidateCache(const rational &start_range, const rational &e } } -QVariant TrackOutput::Value(NodeOutput *output, const rational &in, const rational &out) +QVariant TrackOutput::Value(NodeOutput *output) { if (output == track_output_) { // Set track output correctly return PtrToValue(this); - } else if (output == buffer_output()) { + /*} else if (output == buffer_output()) { ValidateCurrentBlock(in); if (current_block_ != this) { @@ -205,11 +205,11 @@ QVariant TrackOutput::Value(NodeOutput *output, const rational &in, const ration } // No texture is valid - return 0; + return 0;*/ } // Run default node processing - return Block::Value(output, in, out); + return Block::Value(output); } void TrackOutput::InsertBlockBetweenBlocks(Block *block, Block *before, Block *after) diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index f72913eb5..8b97fb16d 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -146,7 +146,7 @@ signals: void BlockRemoved(Block* block); protected: - virtual QVariant Value(NodeOutput* output, const rational& in, const rational &out) override; + virtual QVariant Value(NodeOutput* output) override; private: /** diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 57def7aa2..5ad6c07e1 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -104,14 +104,5 @@ void ViewerOutput::set_audio_params(const AudioParams &audio) rational ViewerOutput::Length() { - return length_input_->get_value(0).value(); -} - -QVariant ViewerOutput::Value(NodeOutput *output, const rational &in, const rational &out) -{ - Q_UNUSED(output) - Q_UNUSED(in) - Q_UNUSED(out) - - return 0; + return length_input_->get_realtime_value_of_connected_output().value(); } diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 52aacbc3f..44aaddd21 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -62,9 +62,6 @@ signals: void SizeChanged(int width, int height); -protected: - virtual QVariant Value(NodeOutput* output, const rational &in, const rational &out) override; - private: NodeInput* texture_input_; diff --git a/app/node/param.cpp b/app/node/param.cpp index 48fb68514..bc3083df9 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -32,9 +32,6 @@ #include "node/output.h" NodeParam::NodeParam(const QString &id) : - in_(-1), - out_(-1), - value_caching_(true), id_(id) { Q_ASSERT(!id_.isEmpty()); @@ -110,16 +107,14 @@ NodeEdgePtr NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input) // that's difficult to diagnose. This makes that issue very clear. Q_ASSERT(output->parent() != input->parent()); - output->parent()->Lock(); - input->parent()->Lock(); + output->parent()->LockUserInput(); + input->parent()->LockUserInput(); output->edges_.append(edge); input->edges_.append(edge); - input->ClearCachedValue(); - - output->parent()->Unlock(); - input->parent()->Unlock(); + output->parent()->UnlockUserInput(); + input->parent()->UnlockUserInput(); // Emit a signal than an edge was added (only one signal needs emitting) emit input->EdgeAdded(edge); @@ -132,16 +127,14 @@ void NodeParam::DisconnectEdge(NodeEdgePtr edge) NodeOutput* output = edge->output(); NodeInput* input = edge->input(); - output->parent()->Lock(); - input->parent()->Lock(); + output->parent()->LockUserInput(); + input->parent()->LockUserInput(); output->edges_.removeAll(edge); input->edges_.removeAll(edge); - input->ClearCachedValue(); - - output->parent()->Unlock(); - input->parent()->Unlock(); + output->parent()->UnlockUserInput(); + input->parent()->UnlockUserInput(); emit input->EdgeRemoved(edge); } @@ -228,29 +221,6 @@ QByteArray NodeParam::ValueToBytes(const NodeParam::DataType &type, const QVaria return QByteArray(); } -void NodeParam::ClearCachedValue() -{ - // Since get_value() will (read: should) never receive a negative number, this will effectively invalidate any value - // currently cached - in_ = -1; - out_ = -1; -} - -const rational &NodeParam::LastRequestedIn() -{ - return in_; -} - -bool NodeParam::ValueCachingEnabled() -{ - return value_caching_; -} - -void NodeParam::SetValueCachingEnabled(bool enabled) -{ - value_caching_ = enabled; -} - template QByteArray NodeParam::ValueToBytesInternal(const QVariant &v) { diff --git a/app/node/param.h b/app/node/param.h index 40a832442..70ecf3a1e 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -229,19 +229,6 @@ public: */ static QByteArray ValueToBytes(const DataType &type, const QVariant& value); - /** - * @brief Clear the cached value - */ - void ClearCachedValue(); - - /** - * @brief Retrieve the last time this parameter had a value requested from - */ - const rational& LastRequestedIn(); - - bool ValueCachingEnabled(); - void SetValueCachingEnabled(bool enabled); - signals: /** * @brief Signal emitted when an edge is added to this parameter @@ -265,22 +252,6 @@ protected: */ QVector edges_; - /** - * @brief Currently cached value - */ - QVariant value_; - - /** - * @brief Last timecodes that a value was requested with - */ - rational in_; - rational out_; - - /** - * @brief Internal value for whether value caching is enabled - */ - bool value_caching_; - /** * @brief Internal name string */ diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index fe1e81e2d..3735192ba 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -103,7 +103,7 @@ QString Sequence::duration() return QString(); } - rational timeline_length = timeline_output_->length_output()->get_value(0, 0).value(); + rational timeline_length = timeline_output_->length_output()->get_realtime_value().value(); int64_t timestamp = olive::time_to_timestamp(timeline_length, video_params_.time_base()); diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index 1bbbb79dd..6d4dbcb20 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -66,9 +66,9 @@ void OpenGLWorker::Render(const NodeDependency &path) // Lock all Nodes to prevent UI changes during this render foreach (Node* dep, all_deps) { - dep->Lock(); + dep->LockUserInput(); } - node->Lock(); + node->LockUserInput(); // FIXME: Write traversal code @@ -77,9 +77,9 @@ void OpenGLWorker::Render(const NodeDependency &path) // Unlock all Nodes so changes can be made again foreach (Node* dep, all_deps) { - dep->Unlock(); + dep->UnlockUserInput(); } - node->Unlock(); + node->UnlockUserInput(); // Now we need the texture done so we call glFinish() functions_->glFinish(); @@ -111,3 +111,7 @@ void OpenGLWorker::FinishInit() qDebug() << "Context in" << ctx_->thread() << "successfully finished"; } + +void OpenGLWorker::RenderAsSibling(const NodeDependency &) +{ +} diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 8ac211f54..6d776abeb 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -61,7 +61,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() { FloatSlider* slider = new FloatSlider(); - slider->SetValue(base_input->get_value(0).toDouble()); + slider->SetValue(base_input->get_value_at_time(0).toDouble()); if (base_input->has_minimum()) { slider->SetMinimum(base_input->minimum().toDouble()); @@ -78,7 +78,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() } case NodeParam::kVec2: { - QVector2D vec2 = base_input->get_value(0).value(); + QVector2D vec2 = base_input->get_value_at_time(0).value(); FloatSlider* x_slider = new FloatSlider(); x_slider->SetValue(static_cast(vec2.x())); @@ -93,7 +93,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() } case NodeParam::kVec3: { - QVector3D vec3 = base_input->get_value(0).value(); + QVector3D vec3 = base_input->get_value_at_time(0).value(); FloatSlider* x_slider = new FloatSlider(); x_slider->SetValue(static_cast(vec3.x())); @@ -113,7 +113,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() } case NodeParam::kVec4: { - QVector4D vec4 = base_input->get_value(0).value(); + QVector4D vec4 = base_input->get_value_at_time(0).value(); FloatSlider* x_slider = new FloatSlider(); x_slider->SetValue(static_cast(vec4.x())); @@ -171,7 +171,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() footage_combobox->SetRoot(pp->project()->root()); // Use multiple values - footage_combobox->SetFootage(base_input->get_value(0).value()); + footage_combobox->SetFootage(base_input->get_value_at_time(0).value()); connect(footage_combobox, SIGNAL(FootageChanged(StreamPtr)), this, SLOT(WidgetCallback())); // End test code @@ -201,14 +201,14 @@ void NodeParamViewWidgetBridge::WidgetCallback() { // Widget is a IntegerSlider IntegerSlider* int_slider = static_cast(sender()); - input->set_value(int_slider->GetValue()); + input->set_value_at_time(0, int_slider->GetValue()); break; } case NodeParam::kFloat: { // Widget is a FloatSlider FloatSlider* float_slider = static_cast(sender()); - input->set_value(float_slider->GetValue()); + input->set_value_at_time(0, float_slider->GetValue()); break; } case NodeParam::kVec2: @@ -216,7 +216,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() // Widgets are two FloatSliders FloatSlider* slider = static_cast(sender()); - QVector2D val = input->get_value(0).value(); + QVector2D val = input->get_value_at_time(0).value(); if (slider == widgets_.at(0)) { // Slider is X slider @@ -226,7 +226,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() val.setY(static_cast(slider->GetValue())); } - input->set_value(val); + input->set_value_at_time(0, val); break; } case NodeParam::kVec3: @@ -234,7 +234,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() // Widgets are three FloatSliders FloatSlider* slider = static_cast(sender()); - QVector3D val = input->get_value(0).value(); + QVector3D val = input->get_value_at_time(0).value(); if (slider == widgets_.at(0)) { // Slider is X slider @@ -247,7 +247,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() val.setZ(static_cast(slider->GetValue())); } - input->set_value(val); + input->set_value_at_time(0, val); break; } case NodeParam::kVec4: @@ -255,7 +255,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() // Widgets are three FloatSliders FloatSlider* slider = static_cast(sender()); - QVector4D val = input->get_value(0).value(); + QVector4D val = input->get_value_at_time(0).value(); if (slider == widgets_.at(0)) { // Slider is X slider @@ -271,7 +271,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() val.setW(static_cast(slider->GetValue())); } - input->set_value(val); + input->set_value_at_time(0, val); break; } case NodeParam::kFile: @@ -284,28 +284,28 @@ void NodeParamViewWidgetBridge::WidgetCallback() { // Sender is a QLineEdit QLineEdit* line_edit = static_cast(sender()); - input->set_value(line_edit->text()); + input->set_value_at_time(0, line_edit->text()); break; } case NodeParam::kBoolean: { // Widget is a QCheckBox QCheckBox* check_box = static_cast(sender()); - input->set_value(check_box->isChecked()); + input->set_value_at_time(0, check_box->isChecked()); break; } case NodeParam::kFont: { // Widget is a QFontComboBox QFontComboBox* font_combobox = static_cast(sender()); - input->set_value(font_combobox->currentFont()); + input->set_value_at_time(0, font_combobox->currentFont()); break; } case NodeParam::kFootage: { // Widget is a FootageComboBox FootageComboBox* footage_combobox = static_cast(sender()); - input->set_value(QVariant::fromValue(footage_combobox->SelectedFootage())); + input->set_value_at_time(0, QVariant::fromValue(footage_combobox->SelectedFootage())); break; } }