diff --git a/app/node/node.cpp b/app/node/node.cpp index da1359e15..b2b02d807 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -48,7 +48,8 @@ Node::Node(bool create_default_output) : override_color_(-1), last_change_time_(0), folder_(nullptr), - operation_stack_(0) + operation_stack_(0), + cache_result_(false) { if (create_default_output) { AddOutput(); diff --git a/app/node/node.h b/app/node/node.h index fd3bb626f..86e4d2f54 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -756,6 +756,16 @@ public: folder_ = folder; } + bool GetCacheTextures() const + { + return cache_result_; + } + + void SetCacheTextures(bool e) + { + cache_result_ = e; + } + static const QString kDefaultOutput; protected: @@ -1191,6 +1201,8 @@ private: int operation_stack_; + bool cache_result_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 96122ad45..796e96cfe 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -48,6 +48,8 @@ Footage::Footage(const QString &filename) : Clear(); set_filename(filename); + + SetCacheTextures(true); } void Footage::Retranslate() diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 0a5232867..f7d0eb099 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -22,6 +22,7 @@ #include "node.h" #include "render/job/footagejob.h" +#include "render/rendermanager.h" namespace olive { @@ -110,7 +111,7 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const QString& output // By this point, the node should have all the inputs it needs to render correctly NodeValueTable table = n->Value(output, database); - PostProcessTable(n, range, table); + PostProcessTable(n, output, range, table); return table; } @@ -171,10 +172,15 @@ QVariant NodeTraverser::ProcessFrameGeneration(const Node *node, const GenerateJ return QVariant(); } -QVariant NodeTraverser::GetCachedFrame(const Node *node, const rational &time) +void NodeTraverser::SaveCachedTexture(const QByteArray &hash, const QVariant &texture) { - Q_UNUSED(node) - Q_UNUSED(time) + Q_UNUSED(hash) + Q_UNUSED(texture) +} + +QVariant NodeTraverser::GetCachedTexture(const QByteArray& hash) +{ + Q_UNUSED(hash) return QVariant(); } @@ -190,17 +196,23 @@ void NodeTraverser::AddGlobalsToDatabase(NodeValueDatabase &db, const TimeRange& db.Insert(QStringLiteral("global"), global); } -void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params) +void NodeTraverser::PostProcessTable(const Node *node, const QString& output, const TimeRange &range, NodeValueTable &output_params) { bool got_cached_frame = false; + QByteArray cached_node_hash; // Convert footage to image/sample buffers - QVariant cached_frame = GetCachedFrame(node, range.in()); - if (!cached_frame.isNull()) { - output_params.Push(NodeValue::kTexture, cached_frame, node); + if (CanCacheFrames() && node->GetCacheTextures()) { + // This node is set to cache the result, see if we can retrieved a previously cached version + cached_node_hash = RenderManager::Hash(node, output, GetCacheVideoParams(), range.in()); - // No more to do here - got_cached_frame = true; + QVariant cached_frame = GetCachedTexture(cached_node_hash); + if (!cached_frame.isNull()) { + output_params.Push(NodeValue::kTexture, cached_frame, node); + + // No more to do here + got_cached_frame = true; + } } // Strip out any jobs or footage @@ -285,6 +297,11 @@ void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, N output_params.Push(NodeValue::kSamples, value, node); } } + + if (CanCacheFrames() && node->GetCacheTextures() && !got_cached_frame) { + // Save cached texture + SaveCachedTexture(cached_node_hash, output_params.Get(NodeValue::kTexture)); + } } } diff --git a/app/node/traverser.h b/app/node/traverser.h index cb0da8f60..d62c76581 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -59,7 +59,19 @@ protected: virtual QVariant ProcessFrameGeneration(const Node *node, const GenerateJob& job); - virtual QVariant GetCachedFrame(const Node *node, const rational &time); + virtual QVariant GetCachedTexture(const QByteArray& hash); + + virtual void SaveCachedTexture(const QByteArray& hash, const QVariant& texture); + + virtual bool CanCacheFrames() + { + return false; + } + + virtual VideoParams GetCacheVideoParams() + { + return VideoParams(); + } void AddGlobalsToDatabase(NodeValueDatabase& db, const TimeRange &range) const; @@ -69,7 +81,7 @@ protected: } private: - void PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params); + void PostProcessTable(const Node *node, const QString &output, const TimeRange &range, NodeValueTable &output_params); }; diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index f0d56b79f..a7644d636 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -187,14 +187,24 @@ bool FrameHashCache::SaveCacheFrame(const QByteArray& hash, const VideoParams& vparam, int linesize_bytes) const { - QString fn = CachePathName(hash); + return SaveCacheFrame(GetCacheDirectory(), hash, data, vparam, linesize_bytes); +} + +bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) const +{ + return SaveCacheFrame(GetCacheDirectory(), hash, frame); +} + +bool FrameHashCache::SaveCacheFrame(const QString &cache_path, const QByteArray &hash, char *data, const VideoParams &vparam, int linesize_bytes) +{ + QString fn = CachePathName(cache_path, hash); if (SaveCacheFrame(fn, data, vparam, linesize_bytes)) { // Register frame with the disk manager QMetaObject::invokeMethod(DiskManager::instance(), "CreatedFile", Qt::QueuedConnection, - Q_ARG(QString, GetCacheDirectory()), + Q_ARG(QString, cache_path), Q_ARG(QString, fn), Q_ARG(QByteArray, hash)); @@ -204,10 +214,10 @@ bool FrameHashCache::SaveCacheFrame(const QByteArray& hash, } } -bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) const +bool FrameHashCache::SaveCacheFrame(const QString &cache_path, const QByteArray &hash, FramePtr frame) { if (frame) { - return SaveCacheFrame(hash, frame->data(), frame->video_params(), frame->linesize_bytes()); + return SaveCacheFrame(cache_path, hash, frame->data(), frame->video_params(), frame->linesize_bytes()); } else { qWarning() << "Attempted to save a NULL frame to the cache. This may or may not be desirable."; return false; @@ -394,7 +404,7 @@ QString FrameHashCache::CachePathName(const QString &cache_path, const QByteArra return cache_dir.filePath(filename); } -bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoParams &vparam, int linesize_bytes) const +bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoParams &vparam, int linesize_bytes) { if (!VideoParams::FormatIsFloat(vparam.format())) { qCritical() << "Tried to cache frame with non-float pixel format"; diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index 3ff6eb9b7..1b4d64748 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -61,9 +61,11 @@ public: QString CachePathName(const QByteArray &hash) const; static QString CachePathName(const QString& cache_path, const QByteArray &hash); - bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes) const; + static bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes); bool SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes) const; bool SaveCacheFrame(const QByteArray& hash, FramePtr frame) const; + static bool SaveCacheFrame(const QString& cache_path, const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes); + static bool SaveCacheFrame(const QString& cache_path, const QByteArray& hash, FramePtr frame); static FramePtr LoadCacheFrame(const QString& cache_path, const QByteArray& hash); FramePtr LoadCacheFrame(const QByteArray& hash) const; static FramePtr LoadCacheFrame(const QString& fn); diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 0eac197fe..3ea8c5102 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -189,7 +189,7 @@ RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr // Create ticket RenderTicketPtr ticket = std::make_shared(); - ticket->setProperty("cache", Node::PtrToValue(cache)); + ticket->setProperty("cache", cache->GetCacheDirectory()); ticket->setProperty("frame", QVariant::fromValue(frame)); ticket->setProperty("hash", hash); ticket->setProperty("type", kTypeVideoDownload); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 948e78fe0..63bad549c 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -148,11 +148,11 @@ void RenderProcessor::Run() } case RenderManager::kTypeVideoDownload: { - FrameHashCache* cache = Node::ValueToPtr(ticket_->property("cache")); + QString cache = ticket_->property("cache").toString(); FramePtr frame = ticket_->property("frame").value(); QByteArray hash = ticket_->property("hash").toByteArray(); - ticket_->Finish(cache->SaveCacheFrame(hash, frame), false); + ticket_->Finish(FrameHashCache::SaveCacheFrame(cache, hash, frame), false); break; } default: @@ -534,34 +534,62 @@ QVariant RenderProcessor::ProcessFrameGeneration(const Node *node, const Generat return QVariant::fromValue(texture); } -QVariant RenderProcessor::GetCachedFrame(const Node *node, const rational &time) +bool RenderProcessor::CanCacheFrames() { - if (!ticket_->property("cache").toString().isEmpty() - && node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) { - const VideoParams& video_params = ticket_->property("vparam").value(); + return true; +} - QByteArray hash = RenderManager::Hash(node, video_params, time); +QVariant RenderProcessor::GetCachedTexture(const QByteArray& hash) +{ + VideoParams video_params = GetCacheVideoParams(); + QString cache_dir = ticket_->property("cache").toString(); - FramePtr f = FrameHashCache::LoadCacheFrame(ticket_->property("cache").toString(), hash); + FramePtr f = FrameHashCache::LoadCacheFrame(cache_dir, hash); - if (f) { - // The cached frame won't load with the correct divider by default, so we enforce it here - VideoParams p = f->video_params(); + if (f) { + // The cached frame won't load with the correct divider by default, so we enforce it here + VideoParams p = f->video_params(); - p.set_width(f->width() * video_params.divider()); - p.set_height(f->height() * video_params.divider()); - p.set_divider(video_params.divider()); + p.set_width(f->width() * video_params.divider()); + p.set_height(f->height() * video_params.divider()); + p.set_divider(video_params.divider()); - f->set_video_params(p); + f->set_video_params(p); - TexturePtr texture = render_ctx_->CreateTexture(f->video_params(), f->data(), f->linesize_pixels()); - return QVariant::fromValue(texture); - } + TexturePtr texture = render_ctx_->CreateTexture(f->video_params(), f->data(), f->linesize_pixels()); + qDebug() << "Loaded mid-render frame from cache"; + return QVariant::fromValue(texture); } return QVariant(); } +void RenderProcessor::SaveCachedTexture(const QByteArray &hash, const QVariant &tex_var) +{ + // FIXME: Temporarily disabled because I don't know how to ensure that the frame saved here is + // not the main frame. If it is, it'll be saved twice which will waste a lot of cycles. + // At least disabled, the frame will still save, and if nothing else alters the hash, it + // will pick up automatically from GetCachedTexture. + /*if (!tex_var.isNull()) { + QString cache_dir = ticket_->property("cache").toString(); + + if (!cache_dir.isEmpty()) { + TexturePtr texture = tex_var.value(); + FramePtr frame = Frame::Create(); + frame->set_video_params(texture->params()); + frame->allocate(); + render_ctx_->DownloadFromTexture(texture.get(), frame->data(), frame->linesize_pixels()); + FrameHashCache::SaveCacheFrame(cache_dir, hash, frame); + qDebug() << "Saved mid-render frame to cache"; + } + }*/ +} + +VideoParams RenderProcessor::GetCacheVideoParams() +{ + return ticket_->property("vparam").value(); +} + QVector2D RenderProcessor::GenerateResolution() const { // Set resolution to the destination to the "logical" resolution of the destination diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 0e5754b02..469a007ed 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -53,7 +53,13 @@ protected: virtual QVariant ProcessFrameGeneration(const Node *node, const GenerateJob& job) override; - virtual QVariant GetCachedFrame(const Node *node, const rational &time) override; + virtual bool CanCacheFrames() override; + + virtual QVariant GetCachedTexture(const QByteArray &hash) override; + + virtual void SaveCachedTexture(const QByteArray& hash, const QVariant& texture) override; + + virtual VideoParams GetCacheVideoParams() override; virtual QVector2D GenerateResolution() const override;