diff --git a/app/common/rational.h b/app/common/rational.h index 4f3fcf27d..4a8d21382 100644 --- a/app/common/rational.h +++ b/app/common/rational.h @@ -7,6 +7,8 @@ #define RATIONAL_H #include +#include + extern "C" { #include } @@ -132,4 +134,6 @@ private: #define RATIONAL_MIN rational(LONG_MIN, 1) #define RATIONAL_MAX rational(LONG_MAX, 1) +Q_DECLARE_METATYPE(rational) + #endif // RATIONAL_H diff --git a/app/core.cpp b/app/core.cpp index ca0314a43..7833e0685 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -333,6 +333,7 @@ void Core::DeclareTypesForQt() { qRegisterMetaType("Task::Status"); qRegisterMetaType(); + qRegisterMetaType(); } void Core::StartGUI(bool full_screen) diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index f426a81a8..d3c24961e 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -127,8 +127,6 @@ void Block::Refresh() // Update out point by adding this clip's length to the just calculated in point out_point_ = in_point_ + length(); - InvalidateCache(in_point_, out_point_); - emit Refreshed(); } @@ -176,7 +174,7 @@ void Block::set_media_in(const rational &media_in) media_in_ = media_in; // Signal that this clips contents have changed - InvalidateCache(in(), out()); + InvalidateCache(nullptr, in(), out()); } } @@ -189,3 +187,23 @@ QList Block::RunDependencies(NodeOutput* param, const rational & return QList(); } + +rational Block::SequenceToMediaTime(const rational &sequence_time) +{ + // These constants are not considered "values" per se, so we don't modify them + if (sequence_time == RATIONAL_MIN || sequence_time == RATIONAL_MAX) { + return sequence_time; + } + + return sequence_time - in() + media_in(); +} + +rational Block::MediaToSequenceTime(const rational &media_time) +{ + // These constants are not considered "values" per se, so we don't modify them + if (media_time == RATIONAL_MIN || media_time == RATIONAL_MAX) { + return media_time; + } + + return media_time - media_in() + in(); +} diff --git a/app/node/block/block.h b/app/node/block/block.h index 06904c61f..b1d693b9a 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -104,6 +104,10 @@ signals: protected: virtual QVariant Value(NodeOutput* output, const rational& time) override; + rational SequenceToMediaTime(const rational& sequence_time); + + rational MediaToSequenceTime(const rational& media_time); + private: NodeInput* previous_input_; NodeOutput* block_output_; diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index cec0401de..ae44cdc51 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -67,11 +67,6 @@ NodeInput *ClipBlock::texture_input() return texture_input_; } -rational ClipBlock::SequenceToMediaTime(const rational &sequence_time) -{ - return sequence_time - in() + media_in(); -} - QVariant ClipBlock::Value(NodeOutput* param, const rational& time) { QVariant value = Block::Value(param, time); @@ -80,7 +75,7 @@ QVariant ClipBlock::Value(NodeOutput* param, const rational& time) // If the time retrieved is within this block, get texture information if (time >= in() && time < out()) { // We convert the time given (timeline time) to media time - rational media_time = SequenceToMediaTime(time - in() + media_in()); + rational media_time = SequenceToMediaTime(time); // Retrieve texture return texture_input_->get_value(media_time); @@ -90,3 +85,21 @@ QVariant ClipBlock::Value(NodeOutput* param, const rational& time) return Block::Value(param, time); } + +void ClipBlock::InvalidateCache(NodeInput *from, const rational &start_range, const rational &end_range) +{ + // 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); + + // Limit cache invalidation to clip lengths + start = qMax(start, in()); + end = qMin(end, out()); + + Node::InvalidateCache(from, start, end); + } else { + // Otherwise, pass signal along normally + Node::InvalidateCache(from, start_range, end_range); + } +} diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 0c76a2506..f611c1f6c 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -42,12 +42,12 @@ public: NodeInput* texture_input(); + virtual void InvalidateCache(NodeInput *from, const rational &start_range, const rational &end_range) override; + protected: virtual QVariant Value(NodeOutput* output, const rational& time) override; private: - rational SequenceToMediaTime(const rational& sequence_time); - NodeInput* texture_input_; }; diff --git a/app/node/input.cpp b/app/node/input.cpp index e42df1aa4..94f519dbb 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -102,10 +102,10 @@ void NodeInput::set_value(const QVariant &value) if (keyframing()) { // FIXME: Keyframing code using time() } else { + // Not keyframing, so invalidate entire time length keyframes_.first().set_value(value); - // FIXME: Put correct values here - emit ValueChanged(0, 0); + emit ValueChanged(RATIONAL_MIN, RATIONAL_MAX); } } diff --git a/app/node/node.cpp b/app/node/node.cpp index 9ad9323c1..a1e6c9f43 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -54,6 +54,10 @@ void Node::AddParameter(NodeParam *param) connect(param, SIGNAL(EdgeAdded(NodeEdgePtr)), this, SIGNAL(EdgeAdded(NodeEdgePtr))); connect(param, SIGNAL(EdgeRemoved(NodeEdgePtr)), this, SIGNAL(EdgeRemoved(NodeEdgePtr))); + + if (param->type() == NodeParam::kInput) { + connect(param, SIGNAL(ValueChanged(rational, rational)), this, SLOT(InputChanged(rational, rational))); + } } void Node::RemoveParameter(NodeParam *param) @@ -61,8 +65,10 @@ void Node::RemoveParameter(NodeParam *param) delete param; } -void Node::InvalidateCache(const rational &start_range, const rational &end_range) +void Node::InvalidateCache(NodeInput* from, const rational &start_range, const rational &end_range) { + Q_UNUSED(from) + QList params = parameters(); // Loop through all parameters (there should be no children that are not NodeParams) @@ -78,7 +84,12 @@ void Node::InvalidateCache(const rational &start_range, const rational &end_rang // Only send this signal if the Node isn't ignoring invalidate cache signals from this input if (!connected_node->ignore_invalid_cache_inputs_.contains(connected_input)) { - connected_node->InvalidateCache(start_range, end_range); + // Clear values cached in the parameters + connected_input->ClearCachedValue(); + edge->output()->ClearCachedValue(); + + // Send clear cache signal to the Node + connected_node->InvalidateCache(connected_input, start_range, end_range); } } } @@ -287,3 +298,8 @@ bool Node::HasParamWithID(const QString &id) return false; } + +void Node::InputChanged(rational start, rational end) +{ + InvalidateCache(static_cast(sender()), start, end); +} diff --git a/app/node/node.h b/app/node/node.h index cca6967ed..a1112df3d 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -158,6 +158,17 @@ public: */ static T* ValueToPtr(const QVariant& ptr); + /** + * @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and + * requires re-rendering + * + * Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function. + * Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break + * 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(NodeInput* from, const rational& start_range, const rational& end_range); + protected: /** * @brief Add a parameter to this node @@ -175,17 +186,6 @@ protected: */ void RemoveParameter(NodeParam* param); - /** - * @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and - * requires re-rendering - * - * Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function. - * Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break - * 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); - /** * @brief If we receive a signal from NodeInput `input`, don't propagate it. */ @@ -262,6 +262,9 @@ private: * @brief Used for thread safety in Run() */ QMutex lock_; + +private slots: + void InputChanged(rational start, rational end); }; template diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index a58e80351..936306a6c 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -175,6 +175,21 @@ void TrackOutput::InsertBlockBetweenBlocks(Block *block, Block *before, Block *a Block::ConnectBlocks(block, after); } +void TrackOutput::InsertBlockBefore(Block* block, Block* after) +{ + Block* before = after->previous(); + + // If a block precedes this one, just insert between them + if (before != nullptr) { + InsertBlockBetweenBlocks(block, before, after); + } else { + AddBlockToGraph(block); + + // Otherwise, just connect the block since there's no before clip to insert between + Block::ConnectBlocks(block, after); + } +} + void TrackOutput::InsertBlockAfter(Block *block, Block *before) { InsertBlockBetweenBlocks(block, before, before->next()); @@ -279,16 +294,20 @@ void TrackOutput::PlaceBlock(Block *block, rational start) // Check if the placement location is past the end of the timeline if (start >= in()) { + GapBlock* gap = nullptr; + if (start > in()) { // If so, insert a gap here - GapBlock* gap = new GapBlock(); + gap = new GapBlock(); gap->set_length(start - in()); - - // Then append them - AppendBlock(gap); } - AppendBlock(block); + InsertBlockBefore(block, this); + + if (gap != nullptr) { + // Insert gap if we made one before + InsertBlockBefore(gap, block); + } return; } diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 0cf583c23..c88705494 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -88,6 +88,11 @@ public: */ void InsertBlockAfter(Block* block, Block* before); + /** + * @brief Inserts Block before another Block + */ + void InsertBlockBefore(Block* block, Block* after); + /** * @brief Adds Block `block` at the very end of the Sequence after all other clips */ diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 428643cfb..9eefb59a2 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -78,12 +78,12 @@ void ViewerOutput::AttachViewer(ViewerPanel *viewer) } } -void ViewerOutput::InvalidateCache(const rational &start_range, const rational &end_range) +void ViewerOutput::InvalidateCache(NodeInput* from, const rational &start_range, const rational &end_range) { // Update any attached viewer UpdateViewer(); - Node::InvalidateCache(start_range, end_range); + Node::InvalidateCache(from, start_range, end_range); } void ViewerOutput::UpdateViewer() diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 2eecb758a..b1b628515 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -47,7 +47,7 @@ public: void AttachViewer(ViewerPanel* viewer); - virtual void InvalidateCache(const rational &start_range, const rational &end_range) override; + virtual void InvalidateCache(NodeInput* from, const rational &start_range, const rational &end_range) override; protected: virtual QVariant Value(NodeOutput* output, const rational& time) override; diff --git a/app/node/processor/renderer/renderer.cpp b/app/node/processor/renderer/renderer.cpp index 52d20a7c1..d9b2872d3 100644 --- a/app/node/processor/renderer/renderer.cpp +++ b/app/node/processor/renderer/renderer.cpp @@ -36,7 +36,8 @@ RendererProcessor::RendererProcessor() : width_(0), height_(0), divider_(1), - caching_(false) + caching_(false), + last_requested_time_(-1) { texture_input_ = new NodeInput("tex_in"); texture_input_->add_data_input(NodeInput::kTexture); @@ -78,6 +79,8 @@ void RendererProcessor::SetCacheName(const QString &s) QVariant RendererProcessor::Value(NodeOutput* output, const rational& time) { if (output == texture_output_) { + last_requested_time_ = time; + if (!texture_input_->IsConnected()) { // Nothing is connected - nothing to show or render return 0; @@ -117,7 +120,7 @@ void RendererProcessor::Release() Stop(); } -void RendererProcessor::InvalidateCache(const rational &start_range, const rational &end_range) +void RendererProcessor::InvalidateCache(NodeInput* from, const rational &start_range, const rational &end_range) { qDebug() << "[RendererProcessor] Cache invalidated between" << start_range.toDouble() @@ -133,12 +136,17 @@ void RendererProcessor::InvalidateCache(const rational &start_range, const ratio for (rational r=true_start_range;r<=end_range;r+=timebase_) { if (!cache_queue_.contains(r)) { cache_queue_.append(r); + + QString fn = CachePathName(r); + if (QFileInfo::exists(fn)) { + QFile(fn).remove(); + } } } CacheNext(); - Node::InvalidateCache(start_range, end_range); + Node::InvalidateCache(from, start_range, end_range); } void RendererProcessor::SetTimebase(const rational &timebase) @@ -216,6 +224,8 @@ void RendererProcessor::Start() // Create download thread download_threads_[i] = std::make_shared(ctx, effective_width_, effective_height_, format_, mode_); download_threads_[i]->StartThread(QThread::LowPriority); + + connect(download_threads_[i].get(), SIGNAL(Downloaded(const rational&)), this, SLOT(DownloadThreadFinished(const rational&))); } last_download_thread_ = 0; @@ -314,13 +324,9 @@ void RendererProcessor::ThreadCallback() RenderTexturePtr texture = texture_input_->get_value(cache_frame_).value(); - QString fn = CachePathName(cache_frame_); - if (texture == nullptr) { - if (QFileInfo::exists(fn)) { - QFile(fn).remove(); - } - } else { - download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture, fn); + if (texture != nullptr) { + QString fn = CachePathName(cache_frame_); + download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture, fn, cache_frame_); last_download_thread_++; } @@ -338,6 +344,23 @@ void RendererProcessor::ThreadRequestSibling(NodeDependency dep) } } +void RendererProcessor::DownloadThreadFinished(const rational& time) +{ + // Check if we just downloaded (akak finished caching) the frame we're currently on + if (time == last_requested_time_ && texture_output_->IsConnected()) { + // Send invalidate cache signal to all nodes connected to the texture output + QVector edges = texture_output()->edges(); + + texture_output_->ClearCachedValue(); + + foreach (NodeEdgePtr edge, edges) { + edge->input()->parent()->InvalidateCache(edge->input(), + time, + time); + } + } +} + RendererThreadBase* RendererProcessor::CurrentThread() { return dynamic_cast(QThread::currentThread()); diff --git a/app/node/processor/renderer/renderer.h b/app/node/processor/renderer/renderer.h index 8283b2043..cbfd3c454 100644 --- a/app/node/processor/renderer/renderer.h +++ b/app/node/processor/renderer/renderer.h @@ -53,7 +53,7 @@ public: virtual void Release() override; - virtual void InvalidateCache(const rational &start_range, const rational &end_range) override; + virtual void InvalidateCache(NodeInput *from, const rational &start_range, const rational &end_range) override; void SetTimebase(const rational& timebase); @@ -171,6 +171,8 @@ private: QVector download_threads_; int last_download_thread_; + rational last_requested_time_; + RenderTexturePtr master_texture_; private slots: @@ -178,6 +180,8 @@ private slots: void ThreadRequestSibling(NodeDependency dep); + void DownloadThreadFinished(const rational &time); + }; #endif // RENDERER_H diff --git a/app/node/processor/renderer/rendererdownloadthread.cpp b/app/node/processor/renderer/rendererdownloadthread.cpp index a19902972..fda4685a2 100644 --- a/app/node/processor/renderer/rendererdownloadthread.cpp +++ b/app/node/processor/renderer/rendererdownloadthread.cpp @@ -16,12 +16,13 @@ RendererDownloadThread::RendererDownloadThread(QOpenGLContext *share_ctx, { } -void RendererDownloadThread::Queue(RenderTexturePtr texture, const QString& fn) +void RendererDownloadThread::Queue(RenderTexturePtr texture, const QString& fn, const rational& time) { texture_queue_lock_.lock(); texture_queue_.append(texture); download_filenames_.append(fn); + texture_times_.append(time); wait_cond_.wakeAll(); @@ -37,6 +38,7 @@ void RendererDownloadThread::ProcessLoop() RenderTexturePtr working_texture; QString working_filename; + rational working_time; int buffer_size = PixelService::GetBufferSize(render_instance()->format(), render_instance()->width(), @@ -62,6 +64,7 @@ void RendererDownloadThread::ProcessLoop() working_texture = texture_queue_.takeFirst(); working_filename = download_filenames_.takeFirst(); + working_time = texture_times_.takeFirst(); texture_queue_lock_.unlock(); @@ -101,7 +104,7 @@ void RendererDownloadThread::ProcessLoop() out->close(); } - qDebug() << this << "saved" << working_filename; + emit Downloaded(working_time); } diff --git a/app/node/processor/renderer/rendererdownloadthread.h b/app/node/processor/renderer/rendererdownloadthread.h index b950f8a79..f9d7e31e6 100644 --- a/app/node/processor/renderer/rendererdownloadthread.h +++ b/app/node/processor/renderer/rendererdownloadthread.h @@ -13,7 +13,10 @@ public: const olive::PixelFormat& format, const olive::RenderMode& mode); - void Queue(RenderTexturePtr texture, const QString &fn); + void Queue(RenderTexturePtr texture, const QString &fn, const rational &time); + +signals: + void Downloaded(const rational& time); protected: virtual void ProcessLoop() override; @@ -25,6 +28,8 @@ private: QVector download_filenames_; + QVector texture_times_; + QMutex texture_queue_lock_; }; diff --git a/app/widget/slider/sliderbase.cpp b/app/widget/slider/sliderbase.cpp index 9eda9bd6b..2141fd75e 100644 --- a/app/widget/slider/sliderbase.cpp +++ b/app/widget/slider/sliderbase.cpp @@ -31,6 +31,8 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) : mode_(mode), dragged_(false) { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + label_ = new SliderLabel(this); addWidget(label_);