From ba7a10177fc7627c452bb907d87ebb8fcfbf143e Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 9 Sep 2019 04:07:51 +1000 Subject: [PATCH] fixed various caching issues when rapidly changing values --- app/core.cpp | 1 + app/decoder/ffmpeg/ffmpegdecoder.cpp | 1 + app/decoder/frame.cpp | 15 +- app/decoder/frame.h | 5 + app/node/block/block.cpp | 4 +- app/node/block/clip/clip.cpp | 4 +- app/node/color/opacity/opacity.cpp | 2 + app/node/input.cpp | 38 +++- app/node/input.h | 28 +++ app/node/input/media/media.cpp | 106 ++++++----- app/node/input/media/media.h | 5 +- app/node/node.cpp | 40 ++-- app/node/node.h | 14 +- app/node/output.cpp | 8 +- app/node/output.h | 2 + app/node/output/viewer/viewer.cpp | 26 +-- app/node/output/viewer/viewer.h | 4 +- app/node/param.cpp | 6 + app/node/param.h | 7 + app/node/processor/renderer/renderer.cpp | 172 +++++++++++------- app/node/processor/renderer/renderer.h | 26 ++- .../renderer/rendererdownloadthread.cpp | 2 - .../renderer/rendererprocessthread.cpp | 50 ++--- .../renderer/rendererprocessthread.h | 12 +- app/panel/viewer/viewer.cpp | 5 + app/panel/viewer/viewer.h | 2 + .../nodeparamviewwidgetbridge.cpp | 13 +- app/widget/slider/floatslider.cpp | 10 + app/widget/slider/floatslider.h | 4 + app/widget/slider/integerslider.cpp | 10 + app/widget/slider/integerslider.h | 4 + app/widget/slider/sliderbase.cpp | 57 ++++-- app/widget/slider/sliderbase.h | 12 ++ app/widget/timeruler/timeruler.cpp | 2 +- app/widget/timeruler/timeruler.h | 2 +- app/widget/viewer/viewer.cpp | 11 +- app/widget/viewer/viewer.h | 2 + 37 files changed, 495 insertions(+), 217 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 9118e12fb..2b90efdaa 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -337,6 +337,7 @@ void Core::DeclareTypesForQt() qRegisterMetaType("Task::Status"); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); } void Core::StartGUI(bool full_screen) diff --git a/app/decoder/ffmpeg/ffmpegdecoder.cpp b/app/decoder/ffmpeg/ffmpegdecoder.cpp index 795431783..b9a567257 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.cpp +++ b/app/decoder/ffmpeg/ffmpegdecoder.cpp @@ -251,6 +251,7 @@ FramePtr FFmpegDecoder::Retrieve(const rational &timecode, const rational &lengt frame_container->set_height(frame_->height); frame_container->set_format(output_fmt_); frame_container->set_timestamp(rational(frame_->pts * avstream_->time_base.num, avstream_->time_base.den)); + frame_container->set_native_timestamp(frame_->pts); frame_container->allocate(); // Convert pixel format/linesize if necessary diff --git a/app/decoder/frame.cpp b/app/decoder/frame.cpp index fafa082b7..eaa6b1c29 100644 --- a/app/decoder/frame.cpp +++ b/app/decoder/frame.cpp @@ -27,7 +27,10 @@ Frame::Frame() : width_(0), - height_(0) + height_(0), + format_(-1), + timestamp_(0), + native_timestamp_(0) { } @@ -66,6 +69,16 @@ void Frame::set_timestamp(const rational ×tamp) timestamp_ = timestamp; } +const int64_t &Frame::native_timestamp() +{ + return native_timestamp_; +} + +void Frame::set_native_timestamp(const int64_t ×tamp) +{ + native_timestamp_ = timestamp; +} + const int &Frame::format() { return format_; diff --git a/app/decoder/frame.h b/app/decoder/frame.h index 3a27b6b9f..e56fb6be5 100644 --- a/app/decoder/frame.h +++ b/app/decoder/frame.h @@ -65,6 +65,9 @@ public: const rational& timestamp(); void set_timestamp(const rational& timestamp); + const int64_t& native_timestamp(); + void set_native_timestamp(const int64_t& timestamp); + /** * @brief Get frame's format * @@ -110,6 +113,8 @@ private: rational timestamp_; + int64_t native_timestamp_; + }; #endif // FRAME_H diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 5d57b46c5..628aaa1c0 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -109,7 +109,7 @@ void Block::EdgeAddedSlot(NodeEdgePtr edge) RefreshFollowing(); // Entire track will have shifted, so the whole cache needs to be re-validated - InvalidateCache(0, RATIONAL_MAX); + SendInvalidateCache(0, RATIONAL_MAX); } } @@ -182,7 +182,7 @@ void Block::set_media_in(const rational &media_in) media_in_ = media_in; // Signal that this clips contents have changed - InvalidateCache(in(), out()); + SendInvalidateCache(in(), out()); } } diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 8000c140b..083ea7989 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -69,11 +69,9 @@ NodeInput *ClipBlock::texture_input() QVariant ClipBlock::Value(NodeOutput* param, const rational& time) { - QVariant value = Block::Value(param, time); - if (param == texture_output()) { // If the time retrieved is within this block, get texture information - if (time >= in() && time < out()) { + if (texture_input()->IsConnected() && time >= in() && time < out()) { // We convert the time given (timeline time) to media time rational media_time = SequenceToMediaTime(time); diff --git a/app/node/color/opacity/opacity.cpp b/app/node/color/opacity/opacity.cpp index 3d0084f7b..dae3ca234 100644 --- a/app/node/color/opacity/opacity.cpp +++ b/app/node/color/opacity/opacity.cpp @@ -29,6 +29,8 @@ OpacityNode::OpacityNode() opacity_input_ = new NodeInput("opacity_in"); opacity_input_->add_data_input(NodeParam::kFloat); opacity_input_->set_value(100); + opacity_input_->set_minimum(0); + opacity_input_->set_maximum(100); AddParameter(opacity_input_); texture_input_ = new NodeInput("tex_in"); diff --git a/app/node/input.cpp b/app/node/input.cpp index eeb5d5c63..ccc7e767f 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -26,7 +26,9 @@ NodeInput::NodeInput(const QString& id) : NodeParam(id), keyframing_(false), - dependent_(true) + dependent_(true), + has_minimum_(false), + has_maximum_(false) { // Have at least one keyframe/value active at any time keyframes_.append(NodeKeyframe()); @@ -71,7 +73,7 @@ QVariant NodeInput::get_value(const rational& time) { QVariant v; - if (time_ != time) { + if (time_ != time || !value_caching_) { // Retrieve the value if (!edges_.isEmpty()) { // A connection - use the output of the connected Node @@ -128,6 +130,38 @@ void NodeInput::set_dependent(bool d) dependent_ = d; } +const QVariant &NodeInput::minimum() +{ + return minimum_; +} + +bool NodeInput::has_minimum() +{ + return has_minimum_; +} + +void NodeInput::set_minimum(const QVariant &min) +{ + minimum_ = min; + has_minimum_ = true; +} + +const QVariant &NodeInput::maximum() +{ + return maximum_; +} + +bool NodeInput::has_maximum() +{ + return has_maximum_; +} + +void NodeInput::set_maximum(const QVariant &max) +{ + maximum_ = max; + has_maximum_ = true; +} + NodeParam::DataType NodeInput::data_type() { if (inputs_.isEmpty()) { diff --git a/app/node/input.h b/app/node/input.h index 6e042ad80..89636d0c7 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -124,6 +124,14 @@ public: */ void set_dependent(bool d); + const QVariant& minimum(); + bool has_minimum(); + void set_minimum(const QVariant& min); + + const QVariant& maximum(); + bool has_maximum(); + void set_maximum(const QVariant& max); + virtual DataType data_type() override; /** @@ -165,6 +173,26 @@ private: */ bool dependent_; + /** + * @brief Sets whether this param has a minimum value or not + */ + bool has_minimum_; + + /** + * @brief Internal minimum value + */ + QVariant minimum_; + + /** + * @brief Sets whether this param has a maximum value or not + */ + bool has_maximum_; + + /** + * @brief Internal maximum value + */ + QVariant maximum_; + }; #endif // NODEINPUT_H diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index 34a12e192..004da2617 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -34,7 +34,8 @@ MediaInput::MediaInput() : decoder_(nullptr), color_service_(nullptr), pipeline_(nullptr), - ocio_texture_(0) + ocio_texture_(0), + frame_(nullptr) { footage_input_ = new NodeInput("footage_in"); footage_input_->add_data_input(NodeInput::kFootage); @@ -46,6 +47,7 @@ MediaInput::MediaInput() : texture_output_ = new NodeOutput("tex_out"); texture_output_->set_data_type(NodeOutput::kTexture); + texture_output_->SetValueCachingEnabled(false); AddParameter(texture_output_); } @@ -73,6 +75,7 @@ void MediaInput::Release() { internal_tex_.Destroy(); + frame_ = nullptr; decoder_ = nullptr; color_service_ = nullptr; pipeline_ = nullptr; @@ -128,62 +131,67 @@ QVariant MediaInput::Value(NodeOutput *output, const rational &time) return 0; } - // Get frame from Decoder - FramePtr frame = decoder_->Retrieve(time); + // Check if we need to get a frame or not + if (frame_ == nullptr || frame_native_ts_ != decoder_->GetTimestampFromTime(time)) { + // Get frame from Decoder + frame_ = decoder_->Retrieve(time); - if (frame == nullptr) { - return 0; - } - - if (color_service_ == nullptr) { - // FIXME: Hardcoded values for testing - color_service_ = std::make_shared("srgb", OCIO::ROLE_SCENE_LINEAR); - } - - // OpenColorIO v1's color transforms can be done on GPU, which improves performance but reduces accuracy. When - // online, we prefer accuracy over performance so we use the CPU path instead: - // NOTE: OCIO v2 boasts 1:1 results with the CPU and GPU path so this won't be necessary forever - if (renderer->mode() == olive::RenderMode::kOnline) { - // Convert to 32F, which is required for OpenColorIO's color transformation - frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA32F); - - if (alpha_is_associated) { - // Unassociate alpha here if associated - ColorService::DisassociateAlpha(frame); + if (frame_ == nullptr) { + return 0; } - // Transform color to reference space - color_service_->ConvertFrame(frame); + frame_native_ts_ = frame_->native_timestamp(); - if (alpha_is_associated) { - // If alpha was associated, reassociate here - ColorService::ReassociateAlpha(frame); + if (color_service_ == nullptr) { + // FIXME: Hardcoded values for testing + color_service_ = std::make_shared("srgb", OCIO::ROLE_SCENE_LINEAR); + } + + // OpenColorIO v1's color transforms can be done on GPU, which improves performance but reduces accuracy. When + // online, we prefer accuracy over performance so we use the CPU path instead: + // NOTE: OCIO v2 boasts 1:1 results with the CPU and GPU path so this won't be necessary forever + if (renderer->mode() == olive::RenderMode::kOnline) { + // Convert to 32F, which is required for OpenColorIO's color transformation + frame_ = PixelService::ConvertPixelFormat(frame_, olive::PIX_FMT_RGBA32F); + + if (alpha_is_associated) { + // Unassociate alpha here if associated + ColorService::DisassociateAlpha(frame_); + } + + // Transform color to reference space + color_service_->ConvertFrame(frame_); + + if (alpha_is_associated) { + // If alpha was associated, reassociate here + ColorService::ReassociateAlpha(frame_); + } else { + // If alpha was not associated, associate here + ColorService::AssociateAlpha(frame_); + } + } + + // We use an internal texture to bring the texture into GPU space before performing transformations + + // Ensure the texture is the accurate to the frame + if (internal_tex_.width() != frame_->width() + || internal_tex_.height() != frame_->height() + || internal_tex_.format() != frame_->format()) { + internal_tex_.Destroy(); + } + + // Create or upload the new data to the texture + if (!internal_tex_.IsCreated()) { + internal_tex_.Create(renderer->context(), + frame_->width(), + frame_->height(), + static_cast(frame_->format()), + frame_->data()); } else { - // If alpha was not associated, associate here - ColorService::AssociateAlpha(frame); + internal_tex_.Upload(frame_->data()); } } - // We use an internal texture to bring the texture into GPU space before performing transformations - - // Ensure the texture is the accurate to the frame - if (internal_tex_.width() != frame->width() - || internal_tex_.height() != frame->height() - || internal_tex_.format() != frame->format()) { - internal_tex_.Destroy(); - } - - // Create or upload the new data to the texture - if (!internal_tex_.IsCreated()) { - internal_tex_.Create(renderer->context(), - frame->width(), - frame->height(), - static_cast(frame->format()), - frame->data()); - } else { - internal_tex_.Upload(frame->data()); - } - // Create new texture in reference space to send throughout the rest of the graph RenderTexturePtr output_texture = std::make_shared(); diff --git a/app/node/input/media/media.h b/app/node/input/media/media.h index e0240130b..4bd807ba8 100644 --- a/app/node/input/media/media.h +++ b/app/node/input/media/media.h @@ -53,8 +53,6 @@ public: virtual void Hash(QCryptographicHash *hash, NodeOutput* from, const rational &time) override; - - protected: virtual QVariant Value(NodeOutput* output, const rational& time) override; @@ -78,6 +76,9 @@ private: QOpenGLContext* ocio_ctx_; GLuint ocio_texture_; + FramePtr frame_; + int64_t frame_native_ts_; + }; #endif // IMAGE_H diff --git a/app/node/node.cpp b/app/node/node.cpp index 3b91e0871..17f2d5bcc 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -71,30 +71,28 @@ void Node::InvalidateCache(const rational &start_range, const rational &end_rang { Q_UNUSED(from) + ClearCachedValuesInParameters(start_range, end_range); + + SendInvalidateCache(start_range, end_range); +} + +void Node::SendInvalidateCache(const rational &start_range, const rational &end_range) +{ QList params = parameters(); // Loop through all parameters (there should be no children that are not NodeParams) foreach (NodeParam* param, params) { - // If the Node is an output, relay the signal to any Nodes that are connected to it if (param->type() == NodeParam::kOutput) { QVector edges = param->edges(); foreach (NodeEdgePtr edge, edges) { - NodeInput* connected_input = edge->input(); Node* connected_node = connected_input->parent(); - // 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)) { - // Clear values cached in the parameters - connected_input->ClearCachedValue(); - edge->output()->ClearCachedValue(); - - // Send clear cache signal to the Node - connected_node->InvalidateCache(start_range, end_range, connected_input); - } + // Send clear cache signal to the Node + connected_node->InvalidateCache(start_range, end_range, connected_input); } } } @@ -130,11 +128,6 @@ void Node::CopyInputs(Node *source, Node *destination) } } -void Node::IgnoreCacheInvalidationFrom(NodeInput *input) -{ - ignore_invalid_cache_inputs_.append(input); -} - rational Node::LastProcessedTime() { rational t; @@ -161,6 +154,21 @@ NodeOutput *Node::LastProcessedOutput() return o; } +void Node::ClearCachedValuesInParameters(const rational &start_range, const rational &end_range) +{ + Q_UNUSED(start_range) + Q_UNUSED(end_range) + + QList params = parameters(); + + // 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& time) { return Value(output, time); diff --git a/app/node/node.h b/app/node/node.h index 128b623e9..dcd16ec03 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -209,11 +209,6 @@ protected: */ void RemoveParameter(NodeParam* param); - /** - * @brief If we receive a signal from NodeInput `input`, don't propagate it. - */ - void IgnoreCacheInvalidationFrom(NodeInput* input); - /** * @brief The main processing function * @@ -239,6 +234,10 @@ protected: */ NodeOutput* LastProcessedOutput(); + void ClearCachedValuesInParameters(const rational& start_range, const rational& end_range); + + void SendInvalidateCache(const rational& start_range, const rational& end_range); + public slots: signals: @@ -266,11 +265,6 @@ private: */ bool HasParamWithID(const QString& id); - /** - * @brief Internal list of inputs to ignore InvalidateCache() signals from - */ - QList ignore_invalid_cache_inputs_; - /** * @brief The last timecode Process() was called with */ diff --git a/app/node/output.cpp b/app/node/output.cpp index cef60045f..0144ddbb0 100644 --- a/app/node/output.cpp +++ b/app/node/output.cpp @@ -46,7 +46,7 @@ QVariant NodeOutput::get_value(const rational& time) { QVariant v; - if (time_ != time) { + if (time_ != time || !value_caching_) { // Update the value value_ = parent()->Run(this, time); @@ -58,3 +58,9 @@ QVariant NodeOutput::get_value(const rational& time) return v; } +void NodeOutput::push_value(const QVariant &v, const rational &time) +{ + value_ = v; + time_ = time; +} + diff --git a/app/node/output.h b/app/node/output.h index 2c911bf37..b3cfb8f8d 100644 --- a/app/node/output.h +++ b/app/node/output.h @@ -63,6 +63,8 @@ public: */ virtual QVariant get_value(const rational &time); + void push_value(const QVariant& v, const rational& time); + private: DataType data_type_; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 06cf6038f..87ee9273f 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -21,8 +21,7 @@ #include "viewer.h" ViewerOutput::ViewerOutput() : - attached_viewer_(nullptr), - current_time_(0) + attached_viewer_(nullptr) { texture_input_ = new NodeInput("tex_out"); texture_input_->add_data_input(NodeInput::kTexture); @@ -68,6 +67,9 @@ void ViewerOutput::AttachViewer(ViewerPanel *viewer) // Disconnect old viewer if there's one attached if (attached_viewer_ != nullptr) { disconnect(attached_viewer_, SIGNAL(TimeChanged(const rational&)), this, SLOT(ViewerTimeChanged(const rational&))); + + // Clear any existing texture + attached_viewer_->SetTexture(0); } // FIXME: Currently this attaches to ViewerPanels, but should it attached to Viewers instead? @@ -76,24 +78,28 @@ void ViewerOutput::AttachViewer(ViewerPanel *viewer) if (attached_viewer_ != nullptr) { connect(attached_viewer_, SIGNAL(TimeChanged(const rational&)), this, SLOT(ViewerTimeChanged(const rational&))); SetTimebase(timebase_); + + // Update the texture + ViewerTimeChanged(attached_viewer_->GetTime()); } } void ViewerOutput::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from) { - if (start_range == current_time_ || end_range == current_time_) { + Node::InvalidateCache(start_range, end_range, from); + + if (attached_viewer_ != nullptr + && (start_range == attached_viewer_->GetTime() || end_range == attached_viewer_->GetTime())) { // Update any attached viewer - UpdateViewer(); + ForceUpdateViewer(); } - Node::InvalidateCache(start_range, end_range, from); + SendInvalidateCache(start_range, end_range); } -void ViewerOutput::UpdateViewer() +void ViewerOutput::ForceUpdateViewer() { - texture_input_->ClearCachedValue(); - - ViewerTimeChanged(current_time_); + ViewerTimeChanged(attached_viewer_->GetTime()); } QVariant ViewerOutput::Value(NodeOutput *output, const rational &time) @@ -115,6 +121,4 @@ void ViewerOutput::ViewerTimeChanged(const rational &t) } else { attached_viewer_->SetTexture(0); } - - current_time_ = t; } diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 3a0f8a73c..15ea0a2cd 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -53,7 +53,7 @@ protected: virtual QVariant Value(NodeOutput* output, const rational& time) override; private: - void UpdateViewer(); + void ForceUpdateViewer(); NodeInput* texture_input_; @@ -61,8 +61,6 @@ private: rational timebase_; - rational current_time_; - private slots: void ViewerTimeChanged(const rational& t); diff --git a/app/node/param.cpp b/app/node/param.cpp index f888e73cf..d7dd029f9 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -28,6 +28,7 @@ NodeParam::NodeParam(const QString &id) : time_(-1), + value_caching_(true), id_(id) { Q_ASSERT(!id_.isEmpty()); @@ -251,3 +252,8 @@ const rational &NodeParam::LastRequestedTime() { return time_; } + +void NodeParam::SetValueCachingEnabled(bool enabled) +{ + value_caching_ = enabled; +} diff --git a/app/node/param.h b/app/node/param.h index ff7229c6e..b2d18b8ba 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -250,6 +250,8 @@ public: */ const rational& LastRequestedTime(); + void SetValueCachingEnabled(bool enabled); + virtual DataType data_type() = 0; signals: @@ -285,6 +287,11 @@ protected: */ rational time_; + /** + * @brief Internal value for whether value caching is enabled + */ + bool value_caching_; + private: /** * @brief Internal name string diff --git a/app/node/processor/renderer/renderer.cpp b/app/node/processor/renderer/renderer.cpp index 901f1d9cf..394079051 100644 --- a/app/node/processor/renderer/renderer.cpp +++ b/app/node/processor/renderer/renderer.cpp @@ -99,30 +99,23 @@ QVariant RendererProcessor::Value(NodeOutput* output, const rational& time) // Find frame in map if (time_hash_map_.contains(time)) { - QString fn = CachePathName(time_hash_map_[time]); if (QFileInfo::exists(fn)) { - qDebug() << "Reading" << fn; auto in = OIIO::ImageInput::open(fn.toStdString()); if (in) { in->read_image(PixelService::GetPixelFormatInfo(format_).oiio_desc, cache_frame_load_buffer_.data()); in->close(); - qDebug() << "Stopped reading" << fn; master_texture_->Upload(cache_frame_load_buffer_.data()); return QVariant::fromValue(master_texture_); } else { - qDebug() << "OIIO failed to read EXR:" << OIIO::geterror().c_str(); + qWarning() << "OIIO Error:" << OIIO::geterror().c_str(); } - } else { - qDebug() << "Texture did NOT exist"; } - } else { - qDebug() << "Hash map did NOT have this time"; } } @@ -138,14 +131,18 @@ void RendererProcessor::InvalidateCache(const rational &start_range, const ratio { Q_UNUSED(from) + //ClearCachedValuesInParameters(start_range, end_range); + texture_input_->ClearCachedValue(); + length_input_->ClearCachedValue(); + // Adjust range to min/max values rational start_range_adj = qMax(rational(0), start_range); rational end_range_adj = qMin(length_input()->get_value(0).value(), end_range); - qDebug() << "[RendererProcessor] Cache invalidated between" + /*qDebug() << "[RendererProcessor] Cache invalidated between" << start_range_adj.toDouble() << "and" - << end_range_adj.toDouble(); + << end_range_adj.toDouble();*/ // Snap start_range to timebase double start_range_dbl = start_range_adj.toDouble(); @@ -265,10 +262,30 @@ void RendererProcessor::Start() // Ensure this connection is "Queued" so that it always runs in this object's threaded rather than any of the // other threads - connect(threads_[i].get(), SIGNAL(FinishedPath()), this, SLOT(ThreadCallback()), Qt::QueuedConnection); - connect(threads_[i].get(), SIGNAL(RequestSibling(NodeDependency)), this, SLOT(ThreadRequestSibling(NodeDependency)), Qt::QueuedConnection); + connect(threads_.at(i).get(), + SIGNAL(RequestSibling(NodeDependency)), + this, + SLOT(ThreadRequestSibling(NodeDependency)), + Qt::QueuedConnection); } + // Connect first thread (master thread) to the callback + connect(threads_.first().get(), + SIGNAL(CachedFrame(RenderTexturePtr, const rational&, const QByteArray&)), + this, + SLOT(ThreadCallback(RenderTexturePtr, const rational&, const QByteArray&)), + Qt::QueuedConnection); + connect(threads_.first().get(), + SIGNAL(FrameExists(const rational&, const QByteArray&)), + this, + SLOT(ThreadFrameAlreadyExists(const rational&, const QByteArray&)), + Qt::QueuedConnection); + connect(threads_.first().get(), + SIGNAL(FrameIgnored()), + this, + SLOT(ThreadIgnoredFrame()), + Qt::QueuedConnection); + download_threads_.resize(background_thread_count); for (int i=0;iQueue(NodeDependency(texture_input_->get_connected_output(), cache_frame_), true); + threads_.first()->Queue(NodeDependency(texture_input_->get_connected_output(), cache_frame), true); caching_ = true; } @@ -366,13 +383,33 @@ QString RendererProcessor::CachePathName(const QByteArray &hash) bool RendererProcessor::HasHash(const QByteArray &hash) { - download_list_mutex_.lock(); + return QFileInfo::exists(CachePathName(hash)); +} - bool dl_list_contains = download_list_.contains(hash); +bool RendererProcessor::IsCaching(const QByteArray &hash) +{ + cache_hash_list_mutex_.lock(); - download_list_mutex_.unlock(); + bool is_caching = cache_hash_list_.contains(hash); - return QFileInfo::exists(CachePathName(hash)) || dl_list_contains; + cache_hash_list_mutex_.unlock(); + + return is_caching; +} + +bool RendererProcessor::TryCache(const QByteArray &hash) +{ + cache_hash_list_mutex_.lock(); + + bool is_caching = cache_hash_list_.contains(hash); + + if (!is_caching) { + cache_hash_list_.append(hash); + } + + cache_hash_list_mutex_.unlock(); + + return !is_caching; } void RendererProcessor::CalculateEffectiveDimensions() @@ -381,68 +418,79 @@ void RendererProcessor::CalculateEffectiveDimensions() effective_height_ = height_ / divider_; } -void RendererProcessor::ThreadCallback() +void RendererProcessor::ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash) { - if (sender() == master_thread_) { - // Threads are all done now, time to proceed - caching_ = false; + // Threads are all done now, time to proceed + caching_ = false; - RenderTexturePtr texture = master_thread_->texture(); + if (texture != nullptr) { + // We received a texture, time to start downloading it + QString fn = CachePathName(hash); - bool hash_exists = HasHash(master_thread_->hash()); + download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture, + fn, + time, + hash); - if (!hash_exists) { - if (texture == nullptr) { - // We didn't receive a texture to download, but the viewer may still need updating - DownloadThreadFinished(cache_frame_, master_thread_->hash()); - } else { - // We received a texture, time to start downloading it - download_list_mutex_.lock(); - download_list_.append(master_thread_->hash()); - download_list_mutex_.unlock(); - - QString fn = CachePathName(master_thread_->hash()); - - download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture, - fn, - cache_frame_, - master_thread_->hash()); - - last_download_thread_++; - } - } - - CacheNext(); + last_download_thread_++; + } else { + // There was no texture here, we must update the viewer + MapHashToTimecode(time, hash); } + + // If the connected output is using this time, signal it to update + if (texture_output_->IsConnected() + && texture_output_->LastRequestedTime() == time) { + texture_output_->push_value(QVariant::fromValue(texture), time); + SendInvalidateCache(time, time); + } + + CacheNext(); } void RendererProcessor::ThreadRequestSibling(NodeDependency dep) { // Try to queue another thread to run this dep in advance - for (int i=0;iQueue(dep, false)) { + for (int i=1;iQueue(dep, false)) { return; } } } -void RendererProcessor::DownloadThreadFinished(const rational& time, const QByteArray& hash) +void RendererProcessor::ThreadFrameAlreadyExists(const rational &time, const QByteArray &hash) +{ + caching_ = false; + + // Update hash map with new hash + MapHashToTimecode(time, hash); + + // Signal output to update value + if (texture_output_->IsConnected() + && texture_output_->LastRequestedTime() == time) { + texture_output_->ClearCachedValue(); + SendInvalidateCache(time, time); + } + + // Start caching the next frame + CacheNext(); +} + +void RendererProcessor::MapHashToTimecode(const rational& time, const QByteArray& hash) { // Insert into hash map time_hash_map_.insert(time, hash); - download_list_mutex_.lock(); - download_list_.removeAll(hash); - download_list_mutex_.unlock(); + cache_hash_list_mutex_.lock(); + cache_hash_list_.removeAll(hash); + cache_hash_list_mutex_.unlock(); +} - // Check if we just downloaded (aka finished caching) the frame we're currently on - if (texture_output_->IsConnected() - && texture_output_->LastRequestedTime() == time) { - texture_output_->ClearCachedValue(); +void RendererProcessor::ThreadIgnoredFrame() +{ + caching_ = false; - Node::InvalidateCache(time, time, nullptr); - } + CacheNext(); } RendererThreadBase* RendererProcessor::CurrentThread() diff --git a/app/node/processor/renderer/renderer.h b/app/node/processor/renderer/renderer.h index e351427ec..c55413383 100644 --- a/app/node/processor/renderer/renderer.h +++ b/app/node/processor/renderer/renderer.h @@ -89,6 +89,16 @@ public: */ bool HasHash(const QByteArray& hash); + /** + * @brief Return whether a frame is currently being cached + */ + bool IsCaching(const QByteArray& hash); + + /** + * @brief Check if a frame is currently being cached, and if not reserve it + */ + bool TryCache(const QByteArray& hash); + /** * @brief Return current instance of a RenderThread (or nullptr if there is none) * @@ -131,6 +141,8 @@ private: */ void CacheNext(); + bool ShouldPushTexture(const rational &time); + /** * @brief Return the path of the cached image at this time */ @@ -174,8 +186,6 @@ private: QString cache_id_; bool caching_; - RendererProcessThread* master_thread_; - rational cache_frame_; QVector cache_frame_load_buffer_; QVector download_threads_; @@ -185,15 +195,19 @@ private: QMap time_hash_map_; - QMutex download_list_mutex_; - QVector download_list_; + QMutex cache_hash_list_mutex_; + QVector cache_hash_list_; private slots: - void ThreadCallback(); + void ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash); void ThreadRequestSibling(NodeDependency dep); - void DownloadThreadFinished(const rational &time, const QByteArray &hash); + void ThreadFrameAlreadyExists(const rational &time, const QByteArray &hash); + + void MapHashToTimecode(const rational &time, const QByteArray &hash); + + void ThreadIgnoredFrame(); }; diff --git a/app/node/processor/renderer/rendererdownloadthread.cpp b/app/node/processor/renderer/rendererdownloadthread.cpp index 7b6798613..7249366bc 100644 --- a/app/node/processor/renderer/rendererdownloadthread.cpp +++ b/app/node/processor/renderer/rendererdownloadthread.cpp @@ -113,11 +113,9 @@ void RendererDownloadThread::ProcessLoop() std::unique_ptr out = OIIO::ImageOutput::create(working_fn_std); if (out) { - qDebug() << "Writing" << entry.filename; out->open(working_fn_std, spec); out->write_image(format_info.oiio_desc, data_buffer.data()); out->close(); - qDebug() << "Stopped writing" << entry.filename; emit Downloaded(entry.time, entry.hash); } else { diff --git a/app/node/processor/renderer/rendererprocessthread.cpp b/app/node/processor/renderer/rendererprocessthread.cpp index 7b84af879..1bc4ba36b 100644 --- a/app/node/processor/renderer/rendererprocessthread.cpp +++ b/app/node/processor/renderer/rendererprocessthread.cpp @@ -61,16 +61,6 @@ bool RendererProcessThread::Queue(const NodeDependency& dep, bool wait) return true; } -const QByteArray &RendererProcessThread::hash() -{ - return hash_; -} - -RenderTexturePtr RendererProcessThread::texture() -{ - return texture_; -} - void RendererProcessThread::Cancel() { cancelled_ = true; @@ -115,20 +105,27 @@ void RendererProcessThread::ProcessLoop() texture_ = nullptr; - if (!parent_->HasHash(hash_)) { - QList deps = node_to_process->RunDependencies(output_to_process, path_.time()); + bool has_hash = parent_->HasHash(hash_); + bool can_cache = false; - // Ask for other threads to run these deps while we're here - if (!deps.isEmpty()) { - for (int i=1;iTryCache(hash_))) { + + QList deps = node_to_process->RunDependencies(output_to_process, path_.time()); + + // Ask for other threads to run these deps while we're here + if (!deps.isEmpty()) { + for (int i=1;iget_value(path_.time()).value(); + + render_instance()->context()->functions()->glFinish(); } - - // Get the requested value - texture_ = output_to_process->get_value(path_.time()).value(); - - render_instance()->context()->functions()->glFinish(); } foreach (Node* dep, all_deps) { @@ -137,6 +134,15 @@ void RendererProcessThread::ProcessLoop() node_to_process->Unlock(); - emit FinishedPath(); + if (has_hash && !parent_->IsCaching(hash_)) { + // This hash already exists, no need to cache, just record it + emit FrameExists(path_.time(), hash_); + } else if (can_cache) { + // We cached this frame, signal that it will need to be downloaded to disk + emit CachedFrame(texture_, path_.time(), hash_); + } else { + // Some other dork is caching this frame, skip it + emit FrameIgnored(); + } } } diff --git a/app/node/processor/renderer/rendererprocessthread.h b/app/node/processor/renderer/rendererprocessthread.h index 84e7fc81b..4956f3cc2 100644 --- a/app/node/processor/renderer/rendererprocessthread.h +++ b/app/node/processor/renderer/rendererprocessthread.h @@ -38,10 +38,6 @@ public: bool Queue(const NodeDependency &dep, bool wait); - const QByteArray& hash(); - - RenderTexturePtr texture(); - public slots: virtual void Cancel() override; @@ -51,15 +47,17 @@ protected: signals: void RequestSibling(NodeDependency dep); - void FinishedPath(); + void CachedFrame(RenderTexturePtr texture, const rational& time, const QByteArray& hash); + + void FrameExists(const rational& time, const QByteArray& hash); + + void FrameIgnored(); private: RendererProcessor* parent_; NodeDependency path_; - rational time_; - QByteArray hash_; RenderTexturePtr texture_; diff --git a/app/panel/viewer/viewer.cpp b/app/panel/viewer/viewer.cpp index 85de2ab1d..92c352360 100644 --- a/app/panel/viewer/viewer.cpp +++ b/app/panel/viewer/viewer.cpp @@ -74,6 +74,11 @@ void ViewerPanel::SetTimebase(const rational &timebase) viewer_->SetTimebase(timebase); } +rational ViewerPanel::GetTime() +{ + return viewer_->GetTime(); +} + void ViewerPanel::SetTexture(GLuint tex) { viewer_->SetTexture(tex); diff --git a/app/panel/viewer/viewer.h b/app/panel/viewer/viewer.h index e634c06eb..f0dc4506a 100644 --- a/app/panel/viewer/viewer.h +++ b/app/panel/viewer/viewer.h @@ -50,6 +50,8 @@ public: void SetTimebase(const rational& timebase); + rational GetTime(); + public slots: /** * @brief Set the texture to draw and draw it diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 7cd31759b..d463a674e 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -64,9 +64,20 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeParam::kFloat: { FloatSlider* slider = new FloatSlider(); + slider->SetValue(base_input->get_value(0).toDouble()); - widgets_.append(slider); + + if (base_input->has_minimum()) { + slider->SetMinimum(base_input->minimum().toDouble()); + } + + if (base_input->has_maximum()) { + slider->SetMaximum(base_input->maximum().toDouble()); + } + connect(slider, SIGNAL(ValueChanged(double)), this, SLOT(WidgetCallback())); + + widgets_.append(slider); break; } case NodeParam::kVec2: diff --git a/app/widget/slider/floatslider.cpp b/app/widget/slider/floatslider.cpp index 09e64ee80..f351da7d5 100644 --- a/app/widget/slider/floatslider.cpp +++ b/app/widget/slider/floatslider.cpp @@ -36,6 +36,16 @@ void FloatSlider::SetValue(const double &d) SliderBase::SetValue(d); } +void FloatSlider::SetMinimum(const double &d) +{ + SetMinimumInternal(d); +} + +void FloatSlider::SetMaximum(const double &d) +{ + SetMaximumInternal(d); +} + void FloatSlider::SetDecimalPlaces(int i) { decimal_places_ = i; diff --git a/app/widget/slider/floatslider.h b/app/widget/slider/floatslider.h index 9a4c49bee..1f9c4b6f2 100644 --- a/app/widget/slider/floatslider.h +++ b/app/widget/slider/floatslider.h @@ -33,6 +33,10 @@ public: void SetValue(const double& d); + void SetMinimum(const double& d); + + void SetMaximum(const double& d); + void SetDecimalPlaces(int i); signals: diff --git a/app/widget/slider/integerslider.cpp b/app/widget/slider/integerslider.cpp index 9d7b4b1ff..511f2b581 100644 --- a/app/widget/slider/integerslider.cpp +++ b/app/widget/slider/integerslider.cpp @@ -36,6 +36,16 @@ void IntegerSlider::SetValue(const int &v) SliderBase::SetValue(v); } +void IntegerSlider::SetMinimum(const int &d) +{ + SetMinimumInternal(d); +} + +void IntegerSlider::SetMaximum(const int &d) +{ + SetMaximumInternal(d); +} + void IntegerSlider::ConvertValue(QVariant v) { emit ValueChanged(v.toInt()); diff --git a/app/widget/slider/integerslider.h b/app/widget/slider/integerslider.h index b5554b7b8..f5f78116c 100644 --- a/app/widget/slider/integerslider.h +++ b/app/widget/slider/integerslider.h @@ -33,6 +33,10 @@ public: void SetValue(const int& v); + void SetMinimum(const int& d); + + void SetMaximum(const int& d); + signals: void ValueChanged(int); diff --git a/app/widget/slider/sliderbase.cpp b/app/widget/slider/sliderbase.cpp index efd254faf..d63470d9e 100644 --- a/app/widget/slider/sliderbase.cpp +++ b/app/widget/slider/sliderbase.cpp @@ -28,6 +28,8 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) : QStackedWidget(parent), decimal_places_(1), drag_multiplier_(1.0), + has_min_(false), + has_max_(false), mode_(mode), dragged_(false) { @@ -49,16 +51,14 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) : switch (mode_) { case kString: setCursor(Qt::PointingHandCursor); - value_ = ""; + SetValue(""); break; case kInteger: case kFloat: setCursor(Qt::SizeHorCursor); - value_ = 0; + SetValue(0); break; } - - UpdateLabel(value_); } void SliderBase::SetDragMultiplier(const double &d) @@ -77,11 +77,33 @@ const QVariant &SliderBase::Value() void SliderBase::SetValue(const QVariant &v) { - value_ = v; + value_ = ClampValue(v); UpdateLabel(value_); } +void SliderBase::SetMinimumInternal(const QVariant &v) +{ + min_value_ = v; + has_min_ = true; + + // Limit value by this new minimum value + if (value_ < min_value_) { + SetValue(min_value_); + } +} + +void SliderBase::SetMaximumInternal(const QVariant &v) +{ + max_value_ = v; + has_max_ = true; + + // Limit value by this new maximum value + if (value_ > max_value_) { + SetValue(max_value_); + } +} + void SliderBase::changeEvent(QEvent *e) { if (e->type() == QEvent::LanguageChange) { @@ -90,6 +112,19 @@ void SliderBase::changeEvent(QEvent *e) QStackedWidget::changeEvent(e); } +const QVariant &SliderBase::ClampValue(const QVariant &v) +{ + if (has_min_ && v < min_value_) { + return min_value_; + } + + if (has_max_ && v > max_value_) { + return max_value_; + } + + return v; +} + void SliderBase::UpdateLabel(const QVariant &v) { switch (mode_) { @@ -132,14 +167,12 @@ void SliderBase::LabelClicked() // No-op break; case kInteger: - value_ = qRound(dragged_diff_); + SetValue(qRound(dragged_diff_)); break; case kFloat: - value_ = dragged_diff_; + SetValue(dragged_diff_); break; } - - UpdateLabel(value_); } else { // This was a simple click @@ -177,6 +210,8 @@ void SliderBase::LabelDragged(int i) temp_dragged_value_ = dragged_diff_; } + temp_dragged_value_ = ClampValue(temp_dragged_value_); + UpdateLabel(temp_dragged_value_); emit ValueChanged(temp_dragged_value_); break; @@ -210,9 +245,7 @@ void SliderBase::LineEditConfirmed() } if (is_valid) { - value_ = test_val; - - UpdateLabel(value_); + SetValue(test_val); emit ValueChanged(value_); diff --git a/app/widget/slider/sliderbase.h b/app/widget/slider/sliderbase.h index 9251351fc..15964315c 100644 --- a/app/widget/slider/sliderbase.h +++ b/app/widget/slider/sliderbase.h @@ -48,6 +48,10 @@ protected: void SetValue(const QVariant& v); + void SetMinimumInternal(const QVariant& v); + + void SetMaximumInternal(const QVariant& v); + void UpdateLabel(const QVariant& v); virtual void changeEvent(QEvent* e) override; @@ -57,12 +61,20 @@ protected: double drag_multiplier_; private: + const QVariant& ClampValue(const QVariant& v); + SliderLabel* label_; SliderLineEdit* editor_; QVariant value_; + bool has_min_; + QVariant min_value_; + + bool has_max_; + QVariant max_value_; + Mode mode_; bool dragged_; diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index ddcd78910..8068bd1ab 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -92,7 +92,7 @@ void TimeRuler::SetTimebase(const rational &r) update(); } -const int64_t &TimeRuler::Time() +const int64_t &TimeRuler::GetTime() { return time_; } diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h index c95687643..0a1c841db 100644 --- a/app/widget/timeruler/timeruler.h +++ b/app/widget/timeruler/timeruler.h @@ -42,7 +42,7 @@ public: void SetCenteredText(bool c); - const int64_t& Time(); + const int64_t& GetTime(); public slots: void SetTime(const int64_t &r); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 85e420939..ffd012a98 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -91,6 +91,11 @@ const double &ViewerWidget::scale() return ruler_->scale(); } +rational ViewerWidget::GetTime() +{ + return rational(ruler_->GetTime()) * time_base_; +} + void ViewerWidget::SetScale(const double &scale_) { ruler_->SetScale(scale_); @@ -145,7 +150,7 @@ void ViewerWidget::Play() } start_msec_ = QDateTime::currentMSecsSinceEpoch(); - start_timestamp_ = ruler_->Time(); + start_timestamp_ = ruler_->GetTime(); playback_timer_.start(); @@ -170,14 +175,14 @@ void ViewerWidget::PrevFrame() { Pause(); - SetTime(ruler_->Time() - 1); + SetTime(qMax(static_cast(0), ruler_->GetTime() - 1)); } void ViewerWidget::NextFrame() { Pause(); - SetTime(ruler_->Time() + 1); + SetTime(ruler_->GetTime() + 1); } void ViewerWidget::GoToEnd() diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 4137eed8a..ce17aa7d8 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -49,6 +49,8 @@ public: const double& scale(); + rational GetTime(); + void SetScale(const double& scale_); void SetTime(const int64_t& time);