nodes: implemented infrastructure for caching any node
Not accessible through UI yet, but ported Footage node to it (previously we used a hack to ensure Footage textures were cached, now it's a formal infrastructure).
This commit is contained in:
+2
-1
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,6 +48,8 @@ Footage::Footage(const QString &filename) :
|
||||
Clear();
|
||||
|
||||
set_filename(filename);
|
||||
|
||||
SetCacheTextures(true);
|
||||
}
|
||||
|
||||
void Footage::Retranslate()
|
||||
|
||||
+27
-10
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-2
@@ -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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -189,7 +189,7 @@ RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr
|
||||
// Create ticket
|
||||
RenderTicketPtr ticket = std::make_shared<RenderTicket>();
|
||||
|
||||
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);
|
||||
|
||||
@@ -148,11 +148,11 @@ void RenderProcessor::Run()
|
||||
}
|
||||
case RenderManager::kTypeVideoDownload:
|
||||
{
|
||||
FrameHashCache* cache = Node::ValueToPtr<FrameHashCache>(ticket_->property("cache"));
|
||||
QString cache = ticket_->property("cache").toString();
|
||||
FramePtr frame = ticket_->property("frame").value<FramePtr>();
|
||||
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<VideoParams>();
|
||||
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<TexturePtr>();
|
||||
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<VideoParams>();
|
||||
}
|
||||
|
||||
QVector2D RenderProcessor::GenerateResolution() const
|
||||
{
|
||||
// Set resolution to the destination to the "logical" resolution of the destination
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user