From 0df7012c9e873ac58a71f2e0613232551c059c6c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 20 Sep 2021 21:14:15 -0700 Subject: [PATCH] rewrote hashing --- app/node/CMakeLists.txt | 2 + app/node/hashtraverser.cpp | 172 +++++++++++++++++++++++++ app/node/hashtraverser.h | 68 ++++++++++ app/node/traverser.cpp | 97 +++++++------- app/node/traverser.h | 16 +-- app/node/value.h | 4 + app/render/CMakeLists.txt | 1 - app/render/job/shaderjob.h | 5 + app/render/previewautocacher.cpp | 4 +- app/render/rendermanager.cpp | 29 ++--- app/render/rendermanager.h | 6 +- app/render/renderprocessor.cpp | 209 +++++++++++-------------------- app/render/renderprocessor.h | 21 ++-- app/render/stillimagecache.h | 84 ------------- app/task/render/render.cpp | 2 +- 15 files changed, 402 insertions(+), 318 deletions(-) create mode 100644 app/node/hashtraverser.cpp create mode 100644 app/node/hashtraverser.h delete mode 100644 app/render/stillimagecache.h diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index 3160a12d0..d10d5746f 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -34,6 +34,8 @@ set(OLIVE_SOURCES node/globals.h node/graph.cpp node/graph.h + node/hashtraverser.cpp + node/hashtraverser.h node/inputdragger.cpp node/inputdragger.h node/inputimmediate.cpp diff --git a/app/node/hashtraverser.cpp b/app/node/hashtraverser.cpp new file mode 100644 index 000000000..102006304 --- /dev/null +++ b/app/node/hashtraverser.cpp @@ -0,0 +1,172 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "hashtraverser.h" + +#include + +#include "common/filefunctions.h" + +namespace olive { + +#define super NodeTraverser + +HashTraverser::HashTraverser() : + hash_(QCryptographicHash::Sha1) // Appears to be the fastest hashing algorithm +{ +} + +QByteArray HashTraverser::GetHash(const Node *node, const Node::ValueHint &hint, const VideoParams ¶ms, const TimeRange &range) +{ + // Reset hash + hash_.reset(); + texture_ids_.clear(); + + // Set params throughout traverser + SetCacheVideoParams(params); + + // Embed video parameters into this hash + Hash(params.effective_width()); + Hash(params.effective_height()); + Hash(params.format()); + Hash(params.interlacing()); + //Hash(reference); + + // Our overrides will generate a hash from this + NodeValueTable table = GenerateTable(node, hint, range); + NodeValue final_value = GenerateRowValueElement(hint, NodeValue::kTexture, &table); + qDebug() << "finished with result" << final_value.data(); + HashNodeValue(final_value); + + // Return the hash + return hash_.result(); +} + +TexturePtr HashTraverser::ProcessVideoFootage(const FootageJob &stream, const rational &input_time) +{ + Hash(FileFunctions::GetUniqueFileIdentifier(stream.filename())); + Hash(stream.loop_mode()); + Hash(stream.video_params().stream_index()); + Hash(stream.video_params().colorspace()); + Hash(stream.video_params().premultiplied_alpha()); + Hash(GetCacheVideoParams().divider()); + Hash(stream.video_params().video_type() == VideoParams::kVideoTypeStill ? 0 : input_time); + Hash(stream.video_params().video_type()); + + TexturePtr texture = super::ProcessVideoFootage(stream, input_time); + texture_ids_.insert(texture.get(), hash_.result()); + return texture; +} + +SampleBufferPtr HashTraverser::ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time) +{ + Hash(stream.audio_params().stream_index()); + + SampleBufferPtr buf = super::ProcessAudioFootage(stream, input_time); + texture_ids_.insert(buf.get(), hash_.result()); + return buf; +} + +TexturePtr HashTraverser::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) +{ + HashGenerateJob(node, &job); + + Hash(job.GetShaderID()); + Hash(job.GetIterativeInput()); + Hash(job.GetIterationCount()); + + for (auto it=job.GetInterpolationMap().cbegin(); it!=job.GetInterpolationMap().cend(); it++) { + Hash(it.key()); + Hash(it.value()); + } + + TexturePtr texture = super::ProcessShader(node, range, job); + texture_ids_.insert(texture.get(), hash_.result()); + return texture; +} + +SampleBufferPtr HashTraverser::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) +{ + SampleBufferPtr buf = super::ProcessSamples(node, range, job); + texture_ids_.insert(buf.get(), hash_.result()); + return buf; +} + +TexturePtr HashTraverser::ProcessFrameGeneration(const Node *node, const GenerateJob &job) +{ + HashGenerateJob(node, &job); + + TexturePtr texture = super::ProcessFrameGeneration(node, job); + texture_ids_.insert(texture.get(), hash_.result()); + return texture; +} + +void HashTraverser::HashGenerateJob(const Node *node, const GenerateJob *job) +{ + Hash(node->id()); + Hash(job->GetAlphaChannelRequired()); + + for (auto it=job->GetValues().cbegin(); it!=job->GetValues().cend(); it++) { + Hash(it.key()); + HashNodeValue(it.value()); + } +} + +void HashTraverser::Hash(const QByteArray &array) +{ + hash_.addData(array); +} + +void HashTraverser::Hash(const QString &string) +{ + hash_.addData(string.toUtf8()); +} + +void HashTraverser::HashNodeValue(const NodeValue &value) +{ + NodeValue::Type value_type = value.type(); + + if (value_type == NodeValue::kSamples || value_type == NodeValue::kTexture) { + QByteArray id_for_buffer; + if (value_type == NodeValue::kTexture) { + TexturePtr texture = value.data().value(); + id_for_buffer = texture_ids_.value(texture.get()); + } else { + SampleBufferPtr samples = value.data().value(); + id_for_buffer = texture_ids_.value(samples.get()); + } + + if (id_for_buffer.isEmpty()) { + qWarning() << "Found ID-less buffer while hashing, collisions are likely to occur"; + } else { + Hash(id_for_buffer); + } + } else { + Hash(NodeValue::ValueToBytes(value)); + } +} + +template +void HashTraverser::Hash(T value) +{ + hash_.addData(reinterpret_cast(&value), sizeof(value)); +} + +} diff --git a/app/node/hashtraverser.h b/app/node/hashtraverser.h new file mode 100644 index 000000000..556529555 --- /dev/null +++ b/app/node/hashtraverser.h @@ -0,0 +1,68 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef HASHTRAVERSER_H +#define HASHTRAVERSER_H + +#include "traverser.h" + +namespace olive { + +class HashTraverser : public NodeTraverser +{ +public: + HashTraverser(); + + QByteArray GetHash(const Node *node, const Node::ValueHint &hint, const VideoParams ¶ms, const TimeRange &range); + +protected: + virtual TexturePtr ProcessVideoFootage(const FootageJob &stream, const rational &input_time) override; + + virtual SampleBufferPtr ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time); + + virtual TexturePtr ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override; + + virtual SampleBufferPtr ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job); + + virtual TexturePtr ProcessFrameGeneration(const Node *node, const GenerateJob& job) override; + +private: + void HashGenerateJob(const Node *node, const GenerateJob *job); + + void HashFootageJob(); + + template + void Hash(T value); + + void Hash(const QByteArray &array); + + void Hash(const QString &string); + + void HashNodeValue(const NodeValue &value); + + QCryptographicHash hash_; + + QHash texture_ids_; + +}; + +} + +#endif // HASHTRAVERSER_H diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 1c61f1eec..2a7c38b70 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -84,22 +84,26 @@ NodeValue NodeTraverser::GenerateRowValue(const Node *node, const QString &input NodeValue NodeTraverser::GenerateRowValueElement(const Node *node, const QString &input, int element, NodeValueTable *table) { - int value_index = GenerateRowValueElementIndex(node, input, element, table); - - if (value_index == -1) { - return NodeValue(); - } else { - return table->TakeAt(value_index); - } + return GenerateRowValueElement(node->GetValueHintForInput(input, element), node->GetInputDataType(input), table); } -int NodeTraverser::GenerateRowValueElementIndex(const Node *node, const QString &input, int element, const NodeValueTable *table) +NodeValue NodeTraverser::GenerateRowValueElement(const Node::ValueHint &hint, NodeValue::Type preferred_type, NodeValueTable *table) +{ + int value_index = GenerateRowValueElementIndex(hint, preferred_type, table); + + if (value_index == -1) { + value_index = table->Count() - 1; + } + + return table->TakeAt(value_index); +} + +int NodeTraverser::GenerateRowValueElementIndex(const Node::ValueHint &hint, NodeValue::Type preferred_type, const NodeValueTable *table) { - Node::ValueHint hint = node->GetValueHintForInput(input, element); QVector types = hint.type; if (types.isEmpty()) { - types.append(node->GetInputDataType(input)); + types.append(preferred_type); } if (hint.index == -1) { @@ -124,6 +128,11 @@ int NodeTraverser::GenerateRowValueElementIndex(const Node *node, const QString } } +int NodeTraverser::GenerateRowValueElementIndex(const Node *node, const QString &input, int element, const NodeValueTable *table) +{ + return GenerateRowValueElementIndex(node->GetValueHintForInput(input, element), node->GetInputDataType(input), table); +} + NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams ¶ms, const TimeRange &time) { return NodeGlobals(QVector2D(params.width(), params.height()), time); @@ -248,23 +257,23 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR return table; } -QVariant NodeTraverser::ProcessVideoFootage(const FootageJob &stream, const rational &input_time) +TexturePtr NodeTraverser::ProcessVideoFootage(const FootageJob &stream, const rational &input_time) { Q_UNUSED(input_time) // Create dummy texture with footage params - return QVariant::fromValue(std::make_shared(stream.video_params())); + return std::make_shared(stream.video_params()); } -QVariant NodeTraverser::ProcessAudioFootage(const FootageJob& stream, const TimeRange &input_time) +SampleBufferPtr NodeTraverser::ProcessAudioFootage(const FootageJob& stream, const TimeRange &input_time) { Q_UNUSED(stream) Q_UNUSED(input_time) - return QVariant::fromValue(SampleBuffer::Create()); + return SampleBuffer::Create(); } -QVariant NodeTraverser::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) +TexturePtr NodeTraverser::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) { Q_UNUSED(node) Q_UNUSED(range) @@ -273,19 +282,19 @@ QVariant NodeTraverser::ProcessShader(const Node *node, const TimeRange &range, // Create dummy texture with sequence params VideoParams tex_params = video_params_; tex_params.set_channel_count(GetChannelCountFromJob(job)); - return QVariant::fromValue(std::make_shared(tex_params)); + return std::make_shared(tex_params); } -QVariant NodeTraverser::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) +SampleBufferPtr NodeTraverser::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) { Q_UNUSED(node) Q_UNUSED(range) Q_UNUSED(job) - return QVariant::fromValue(SampleBuffer::Create()); + return SampleBuffer::Create(); } -QVariant NodeTraverser::ProcessFrameGeneration(const Node *node, const GenerateJob &job) +TexturePtr NodeTraverser::ProcessFrameGeneration(const Node *node, const GenerateJob &job) { Q_UNUSED(node) Q_UNUSED(job) @@ -293,20 +302,20 @@ QVariant NodeTraverser::ProcessFrameGeneration(const Node *node, const GenerateJ // Create dummy texture with sequence params VideoParams tex_params = video_params_; tex_params.set_channel_count(GetChannelCountFromJob(job)); - return QVariant::fromValue(std::make_shared(tex_params)); + return std::make_shared(tex_params); } -void NodeTraverser::SaveCachedTexture(const QByteArray &hash, const QVariant &texture) +void NodeTraverser::SaveCachedTexture(const QByteArray &hash, TexturePtr texture) { Q_UNUSED(hash) Q_UNUSED(texture) } -QVariant NodeTraverser::GetCachedTexture(const QByteArray& hash) +TexturePtr NodeTraverser::GetCachedTexture(const QByteArray& hash) { Q_UNUSED(hash) - return QVariant(); + return nullptr; } QVector2D NodeTraverser::GenerateResolution() const @@ -320,18 +329,18 @@ void NodeTraverser::PostProcessTable(const Node *node, const Node::ValueHint &hi QByteArray cached_node_hash; // Convert footage to image/sample buffers - if (CanCacheFrames() && node->GetCacheTextures()) { + /*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, hint, GetCacheVideoParams(), range.in()); - QVariant cached_frame = GetCachedTexture(cached_node_hash); - if (!cached_frame.isNull()) { - output_params.Push(NodeValue::kTexture, cached_frame, node); + TexturePtr cached_frame = GetCachedTexture(cached_node_hash); + if (cached_frame) { + output_params.Push(NodeValue::kTexture, QVariant::fromValue(cached_frame), node); // No more to do here got_cached_frame = true; } - } + }*/ // Strip out any jobs or footage QList footage_jobs_to_run; @@ -369,31 +378,19 @@ void NodeTraverser::PostProcessTable(const Node *node, const Node::ValueHint &hi rational footage_time = Footage::AdjustTimeByLoopMode(range.in(), job.loop_mode(), job.length(), job.video_params().video_type(), job.video_params().frame_rate_as_time_base()); if (!footage_time.isNaN()) { - QVariant value = ProcessVideoFootage(job, footage_time); - - if (!value.isNull()) { - output_params.Push(NodeValue::kTexture, value, node); - } + output_params.Push(NodeValue::kTexture, QVariant::fromValue(ProcessVideoFootage(job, footage_time)), node); } } } // Run shaders foreach (const NodeValue& v, shader_jobs_to_run) { - QVariant value = ProcessShader(node, range, v.data().value()); - - if (!value.isNull()) { - output_params.Push(NodeValue::kTexture, value, node); - } + output_params.Push(NodeValue::kTexture, QVariant::fromValue(ProcessShader(node, range, v.data().value())), node); } // Run generate jobs foreach (const NodeValue& v, generate_jobs_to_run) { - QVariant value = ProcessFrameGeneration(node, v.data().value()); - - if (!value.isNull()) { - output_params.Push(NodeValue::kTexture, value, node); - } + output_params.Push(NodeValue::kTexture, QVariant::fromValue(ProcessFrameGeneration(node, v.data().value())), node); } } @@ -403,26 +400,18 @@ void NodeTraverser::PostProcessTable(const Node *node, const Node::ValueHint &hi FootageJob job = v.data().value(); if (job.type() == Track::kAudio) { - QVariant value = ProcessAudioFootage(job, range); - - if (!value.isNull()) { - output_params.Push(NodeValue::kSamples, value, node); - } + output_params.Push(NodeValue::kSamples, QVariant::fromValue(ProcessAudioFootage(job, range)), node); } } // Run any accelerated shader jobs foreach (const NodeValue& v, sample_jobs_to_run) { - QVariant value = ProcessSamples(node, range, v.data().value()); - - if (!value.isNull()) { - output_params.Push(NodeValue::kSamples, value, node); - } + output_params.Push(NodeValue::kSamples, QVariant::fromValue(ProcessSamples(node, range, v.data().value())), node); } if (CanCacheFrames() && node->GetCacheTextures() && !got_cached_frame) { // Save cached texture - SaveCachedTexture(cached_node_hash, output_params.Get(NodeValue::kTexture)); + SaveCachedTexture(cached_node_hash, output_params.Get(NodeValue::kTexture).value()); } } diff --git a/app/node/traverser.h b/app/node/traverser.h index 14bfed6db..7c831bfce 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -45,6 +45,8 @@ public: NodeValue GenerateRowValue(const Node *node, const QString &input, NodeValueTable *table); NodeValue GenerateRowValueElement(const Node *node, const QString &input, int element, NodeValueTable *table); + NodeValue GenerateRowValueElement(const Node::ValueHint &hint, NodeValue::Type preferred_type, NodeValueTable *table); + int GenerateRowValueElementIndex(const Node::ValueHint &hint, NodeValue::Type preferred_type, const NodeValueTable *table); int GenerateRowValueElementIndex(const Node *node, const QString &input, int element, const NodeValueTable *table); static NodeGlobals GenerateGlobals(const VideoParams ¶ms, const TimeRange &time); @@ -70,19 +72,19 @@ protected: virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange& range); - virtual QVariant ProcessVideoFootage(const FootageJob &stream, const rational &input_time); + virtual TexturePtr ProcessVideoFootage(const FootageJob &stream, const rational &input_time); - virtual QVariant ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time); + virtual SampleBufferPtr ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time); - virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job); + virtual TexturePtr ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job); - virtual QVariant ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job); + virtual SampleBufferPtr ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job); - virtual QVariant ProcessFrameGeneration(const Node *node, const GenerateJob& job); + virtual TexturePtr ProcessFrameGeneration(const Node *node, const GenerateJob& job); - virtual QVariant GetCachedTexture(const QByteArray& hash); + virtual TexturePtr GetCachedTexture(const QByteArray& hash); - virtual void SaveCachedTexture(const QByteArray& hash, const QVariant& texture); + virtual void SaveCachedTexture(const QByteArray& hash, TexturePtr texture); virtual bool CanCacheFrames() { diff --git a/app/node/value.h b/app/node/value.h index d7018ac20..4c6959c9b 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -260,6 +260,10 @@ public: * @brief Convert a value from a NodeParam into bytes */ static QByteArray ValueToBytes(Type type, const QVariant& value); + static QByteArray ValueToBytes(const NodeValue &value) + { + return ValueToBytes(value.type(), value.data()); + } static QVector split_normal_value_into_track_values(Type type, const QVariant &value); diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 122aa5422..ad07e6686 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -54,7 +54,6 @@ set(OLIVE_SOURCES render/renderprocessor.cpp render/renderprocessor.h render/shadercode.h - render/stillimagecache.h render/subtitleparams.cpp render/subtitleparams.h render/texture.cpp diff --git a/app/render/job/shaderjob.h b/app/render/job/shaderjob.h index 6c79823e0..3bbbac19d 100644 --- a/app/render/job/shaderjob.h +++ b/app/render/job/shaderjob.h @@ -72,6 +72,11 @@ public: return interpolation_.value(id, Texture::kDefaultInterpolation); } + const QHash &GetInterpolationMap() const + { + return interpolation_; + } + void SetInterpolation(const NodeInput& input, Texture::Interpolation interp) { interpolation_.insert(input.input(), interp); diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 699d6aa25..de7a9648d 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -69,7 +69,9 @@ QVector PreviewAutoCacher::GenerateHashes(ViewerOut const rational &time = times.at(i); // See if hash already exists in disk cache - QByteArray hash = RenderManager::Hash(viewer->GetConnectedTextureOutput(), viewer->GetValueHintForInput(ViewerOutput::kTextureInput), viewer->GetVideoParams(), time); + QByteArray hash = RenderManager::Hash(viewer, + viewer->GetVideoParams(), + time); // Check memory list since disk checking is slow bool hash_exists = existing_hashes.contains(hash); diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 47fc83d71..8fb2496ab 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -26,6 +26,7 @@ #include "config/config.h" #include "core.h" +#include "node/hashtraverser.h" #include "render/opengl/openglrenderer.h" #include "render/rendererthreadwrapper.h" #include "renderprocessor.h" @@ -53,7 +54,6 @@ RenderManager::RenderManager(QObject *parent) : context_->Init(); context_->PostInit(); - still_cache_ = new StillImageCache(); decoder_cache_ = new DecoderCache(); shader_cache_ = new ShaderCache(); default_shader_ = context_->CreateNativeShader(ShaderCode(QString(), QString())); @@ -64,7 +64,6 @@ RenderManager::RenderManager(QObject *parent) : } else { qCritical() << "Tried to initialize unknown graphics backend"; context_ = nullptr; - still_cache_ = nullptr; decoder_cache_ = nullptr; } } @@ -76,7 +75,6 @@ RenderManager::~RenderManager() delete shader_cache_; delete decoder_cache_; - delete still_cache_; context_->Destroy(); context_->PostDestroy(); @@ -104,24 +102,13 @@ void RenderManager::ClearOldDecoders() QByteArray RenderManager::Hash(const Node *n, const Node::ValueHint &output, const VideoParams ¶ms, const rational &time) { - QCryptographicHash hasher(QCryptographicHash::Sha1); + HashTraverser hasher; + return hasher.GetHash(n, output, params, TimeRange(time, time + params.frame_rate_as_time_base())); +} - // Embed video parameters into this hash - int width = params.effective_width(); - int height = params.effective_height(); - VideoParams::Format format = params.format(); - VideoParams::Interlacing interlacing = params.interlacing(); - - hasher.addData(reinterpret_cast(&width), sizeof(width)); - hasher.addData(reinterpret_cast(&height), sizeof(height)); - hasher.addData(reinterpret_cast(&format), sizeof(format)); - hasher.addData(reinterpret_cast(&interlacing), sizeof(interlacing)); - - if (n) { - Node::Hash(n, output, hasher, NodeTraverser::GenerateGlobals(params, time), params); - } - - return hasher.result(); +QByteArray RenderManager::Hash(ViewerOutput *viewer, const VideoParams ¶ms, const rational &time) +{ + return Hash(viewer->GetConnectedTextureOutput(), viewer->GetValueHintForInput(ViewerOutput::kTextureInput), params, time); } RenderTicketPtr RenderManager::RenderFrame(ViewerOutput *viewer, ColorManager* color_manager, @@ -236,7 +223,7 @@ RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr void RenderManager::RunTicket(RenderTicketPtr ticket) const { - RenderProcessor::Process(ticket, context_, still_cache_, decoder_cache_, shader_cache_, default_shader_); + RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_, default_shader_); } } diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index b6bd5ba8a..ba5f7e7ee 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -31,7 +31,6 @@ #include "node/traverser.h" #include "render/renderer.h" #include "rendercache.h" -#include "stillimagecache.h" #include "threading/threadpool.h" namespace olive { @@ -65,9 +64,10 @@ public: } /** - * @brief Generate a unique identifier for a certain node at a certain time + * @brief Generate a unique identifier for a certain node at a cconst Node *n, const Node::ValueHint &outputertain time */ static QByteArray Hash(const Node *n, const Node::ValueHint &output, const VideoParams ¶ms, const rational &time); + static QByteArray Hash(ViewerOutput *viewer, const VideoParams ¶ms, const rational &time); /** * @brief Asynchronously generate a frame at a given time @@ -137,8 +137,6 @@ private: Backend backend_; - StillImageCache* still_cache_; - DecoderCache* decoder_cache_; ShaderCache* shader_cache_; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index bcd202972..757e581e6 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -32,10 +32,11 @@ namespace olive { -RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache *shader_cache, QVariant default_shader) : +#define super NodeTraverser + +RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache* decoder_cache, ShaderCache *shader_cache, QVariant default_shader) : ticket_(ticket), render_ctx_(render_ctx), - still_image_cache_(still_image_cache), decoder_cache_(decoder_cache), shader_cache_(shader_cache), default_shader_(default_shader) @@ -47,8 +48,7 @@ TexturePtr RenderProcessor::GenerateTexture(const rational &time, const rational ViewerOutput* viewer = Node::ValueToPtr(ticket_->property("viewer")); NodeValueTable table; - Node *texture_output = viewer->GetConnectedTextureOutput(); - if (texture_output) { + if (Node *texture_output = viewer->GetConnectedTextureOutput()) { table = GenerateTable(texture_output, viewer->GetValueHintForInput(ViewerOutput::kTextureInput), TimeRange(time, time + frame_length)); } @@ -169,8 +169,7 @@ void RenderProcessor::Run() TimeRange time = ticket_->property("time").value(); NodeValueTable table; - Node *texture_output = viewer->GetConnectedSampleOutput(); - if (texture_output) { + if (Node *texture_output = viewer->GetConnectedSampleOutput()) { table = GenerateTable(texture_output, viewer->GetValueHintForInput(ViewerOutput::kSamplesInput),time); } @@ -231,9 +230,9 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c return decoder.decoder; } -void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, StillImageCache *still_image_cache, DecoderCache *decoder_cache, ShaderCache *shader_cache, QVariant default_shader) +void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache *decoder_cache, ShaderCache *shader_cache, QVariant default_shader) { - RenderProcessor p(ticket, render_ctx, still_image_cache, decoder_cache, shader_cache, default_shader); + RenderProcessor p(ticket, render_ctx, decoder_cache, shader_cache, default_shader); p.Run(); } @@ -322,19 +321,17 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim return merged_table; } else { - return NodeTraverser::GenerateBlockTable(track, range); + return super::GenerateBlockTable(track, range); } } -QVariant RenderProcessor::ProcessVideoFootage(const FootageJob &stream, const rational &input_time) +TexturePtr RenderProcessor::ProcessVideoFootage(const FootageJob &stream, const rational &input_time) { if (ticket_->property("type").value() != RenderManager::kTypeVideo) { // Video cannot contribute to audio, so we do nothing here - return QVariant(); + return super::ProcessVideoFootage(stream, input_time); } - TexturePtr value = nullptr; - // Check the still frame cache. On large frames such as high resolution still images, uploading // and color managing them for every frame is a waste of time, so we implement a small cache here // to optimize such a situation @@ -360,132 +357,78 @@ QVariant RenderProcessor::ProcessVideoFootage(const FootageJob &stream, const ra Decoder::CodecStream default_codec_stream(stream.filename(), stream_data.stream_index()); - StillImageCache::EntryPtr want_entry = std::make_shared( - nullptr, - default_codec_stream, - ColorProcessor::GenerateID(color_manager, using_colorspace, color_manager->GetReferenceColorSpace()), - stream_data.premultiplied_alpha(), - footage_divider, - (stream_data.video_type() == VideoParams::kVideoTypeStill) ? 0 : input_time, - true); + QString decoder_id = stream.decoder(); - bool found_existing = false; + DecoderPtr decoder = nullptr; - still_image_cache_->mutex()->lock(); - - foreach (StillImageCache::EntryPtr e, still_image_cache_->entries()) { - if (StillImageCache::CompareEntryMetadata(want_entry, e)) { - // Found an exact match of the texture we want in the cache. See if it's working or if it's - // ready. - want_entry = e; - found_existing = true; - - while (want_entry->working) { - still_image_cache_->wait_cond()->wait(still_image_cache_->mutex()); - } - - value = want_entry->texture; - break; - } - } - - if (value) { - // Found the texture, we can release the cache now - still_image_cache_->mutex()->unlock(); + if (stream_data.video_type() == VideoParams::kVideoTypeVideo) { + decoder = ResolveDecoderFromInput(decoder_id, default_codec_stream); } else { - // Wasn't in still image cache, so we'll have to retrieve it from the decoder + // Since image sequences involve multiple files, we don't engage the decoder cache + decoder = Decoder::CreateFromID(decoder_id); - // Let other processors know we're getting this texture (want_entry's `working` field is - // already set to true in the initializer above) - if (!found_existing) { - still_image_cache_->PushEntry(want_entry); - } + QString frame_filename; - still_image_cache_->mutex()->unlock(); - - QString decoder_id = stream.decoder(); - - DecoderPtr decoder = nullptr; - - if (stream_data.video_type() == VideoParams::kVideoTypeVideo) { - decoder = ResolveDecoderFromInput(decoder_id, default_codec_stream); + if (stream_data.video_type() == VideoParams::kVideoTypeImageSequence) { + int64_t frame_number = stream_data.get_time_in_timebase_units(input_time); + frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); } else { - // Since image sequences involve multiple files, we don't engage the decoder cache - decoder = Decoder::CreateFromID(decoder_id); - - QString frame_filename; - - if (stream_data.video_type() == VideoParams::kVideoTypeImageSequence) { - int64_t frame_number = stream_data.get_time_in_timebase_units(input_time); - frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); - } else { - frame_filename = stream.filename(); - } - - // Decoder will close automatically since it's a stream_ptr - decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index())); + frame_filename = stream.filename(); } - if (decoder) { - Decoder::RetrieveVideoParams p; - p.divider = footage_divider; - p.src_interlacing = stream_data.interlacing(); - p.dst_interlacing = GetCacheVideoParams().interlacing(); + // Decoder will close automatically since it's a stream_ptr + decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index())); + } - FramePtr frame = decoder->RetrieveVideo((stream_data.video_type() == VideoParams::kVideoTypeVideo) ? input_time : Decoder::kAnyTimecode, p); + if (decoder) { + Decoder::RetrieveVideoParams p; + p.divider = footage_divider; + p.src_interlacing = stream_data.interlacing(); + p.dst_interlacing = GetCacheVideoParams().interlacing(); - if (frame) { - // Return a texture from the derived class - TexturePtr unmanaged_texture = render_ctx_->CreateTexture(frame->video_params(), - frame->data(), - frame->linesize_pixels()); + FramePtr frame = decoder->RetrieveVideo((stream_data.video_type() == VideoParams::kVideoTypeVideo) ? input_time : Decoder::kAnyTimecode, p); - // We convert to our rendering pixel format, since that will always be float-based which - // is necessary for correct color conversion - VideoParams managed_params = frame->video_params(); - managed_params.set_format(render_params.format()); - managed_params.set_pixel_aspect_ratio(stream_data.pixel_aspect_ratio()); - managed_params.set_interlacing(stream_data.interlacing()); - value = render_ctx_->CreateTexture(managed_params); + if (frame) { + // Return a texture from the derived class + TexturePtr unmanaged_texture = render_ctx_->CreateTexture(frame->video_params(), + frame->data(), + frame->linesize_pixels()); - ColorProcessorPtr processor = ColorProcessor::Create(color_manager, - using_colorspace, - color_manager->GetReferenceColorSpace()); + // We convert to our rendering pixel format, since that will always be float-based which + // is necessary for correct color conversion + VideoParams managed_params = frame->video_params(); + managed_params.set_format(render_params.format()); + managed_params.set_pixel_aspect_ratio(stream_data.pixel_aspect_ratio()); + managed_params.set_interlacing(stream_data.interlacing()); + TexturePtr value = render_ctx_->CreateTexture(managed_params); - Renderer::AlphaAssociated alpha_assoc; - if (stream_data.channel_count() != VideoParams::kRGBAChannelCount - || stream_data.colorspace() == color_manager->GetReferenceColorSpace()) { - alpha_assoc = Renderer::kAlphaNone; - } else if (stream_data.premultiplied_alpha()) { - alpha_assoc = Renderer::kAlphaAssociated; - } else { - alpha_assoc = Renderer::kAlphaUnassociated; - } + ColorProcessorPtr processor = ColorProcessor::Create(color_manager, + using_colorspace, + color_manager->GetReferenceColorSpace()); - render_ctx_->BlitColorManaged(processor, unmanaged_texture, - alpha_assoc, - value.get()); - - still_image_cache_->mutex()->lock(); - - // Put this into the image cache instead - want_entry->texture = value; - want_entry->working = false; - - still_image_cache_->wait_cond()->wakeAll(); - - still_image_cache_->mutex()->unlock(); + Renderer::AlphaAssociated alpha_assoc; + if (stream_data.channel_count() != VideoParams::kRGBAChannelCount + || stream_data.colorspace() == color_manager->GetReferenceColorSpace()) { + alpha_assoc = Renderer::kAlphaNone; + } else if (stream_data.premultiplied_alpha()) { + alpha_assoc = Renderer::kAlphaAssociated; + } else { + alpha_assoc = Renderer::kAlphaUnassociated; } + + render_ctx_->BlitColorManaged(processor, unmanaged_texture, + alpha_assoc, + value.get()); + + return value; } } - return QVariant::fromValue(value); + return super::ProcessVideoFootage(stream, input_time); } -QVariant RenderProcessor::ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time) +SampleBufferPtr RenderProcessor::ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time) { - QVariant value; - DecoderPtr decoder = ResolveDecoderFromInput(stream.decoder(), Decoder::CodecStream(stream.filename(), stream.audio_params().stream_index())); if (decoder) { @@ -497,16 +440,16 @@ QVariant RenderProcessor::ProcessAudioFootage(const FootageJob &stream, const Ti static_cast(ticket_->property("mode").toInt())); if (status.status == Decoder::kOK && status.samples) { - value = QVariant::fromValue(status.samples); + return status.samples; } else if (status.status == Decoder::kWaitingForConform) { ticket_->setProperty("incomplete", true); } } - return value; + return super::ProcessAudioFootage(stream, input_time); } -QVariant RenderProcessor::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) +TexturePtr RenderProcessor::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) { Q_UNUSED(range) @@ -522,7 +465,7 @@ QVariant RenderProcessor::ProcessShader(const Node *node, const TimeRange &range if (shader.isNull()) { // Couldn't find or build the shader required - return QVariant(); + return super::ProcessShader(node, range, job); } } @@ -535,13 +478,13 @@ QVariant RenderProcessor::ProcessShader(const Node *node, const TimeRange &range // Run shader render_ctx_->BlitToTexture(shader, job, destination.get()); - return QVariant::fromValue(destination); + return destination; } -QVariant RenderProcessor::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) +SampleBufferPtr RenderProcessor::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) { if (!job.samples() || !job.samples()->is_allocated()) { - return QVariant(); + super::ProcessSamples(node, range, job); } SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(job.samples()->audio_params(), job.samples()->sample_count()); @@ -568,10 +511,10 @@ QVariant RenderProcessor::ProcessSamples(const Node *node, const TimeRange &rang i); } - return QVariant::fromValue(output_buffer); + return output_buffer; } -QVariant RenderProcessor::ProcessFrameGeneration(const Node *node, const GenerateJob &job) +TexturePtr RenderProcessor::ProcessFrameGeneration(const Node *node, const GenerateJob &job) { FramePtr frame = Frame::Create(); @@ -586,7 +529,7 @@ QVariant RenderProcessor::ProcessFrameGeneration(const Node *node, const Generat frame->data(), frame->linesize_pixels()); - return QVariant::fromValue(texture); + return texture; } bool RenderProcessor::CanCacheFrames() @@ -594,24 +537,24 @@ bool RenderProcessor::CanCacheFrames() return ticket_->property("type").value() == RenderManager::kTypeVideo; } -QVariant RenderProcessor::GetCachedTexture(const QByteArray& hash) +TexturePtr RenderProcessor::GetCachedTexture(const QByteArray& hash) { QString cache_dir = ticket_->property("cache").toString(); if (cache_dir.isEmpty()) { - return QVariant(); + return nullptr; } FramePtr f = FrameHashCache::LoadCacheFrame(cache_dir, hash); if (f) { TexturePtr texture = render_ctx_->CreateTexture(f->video_params(), f->data(), f->linesize_pixels()); - return QVariant::fromValue(texture); + return texture; } - return QVariant(); + return nullptr; } -void RenderProcessor::SaveCachedTexture(const QByteArray &hash, const QVariant &tex_var) +void RenderProcessor::SaveCachedTexture(const QByteArray &hash, TexturePtr 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. diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 2372623b7..213b2df16 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -25,7 +25,6 @@ #include "node/traverser.h" #include "render/renderer.h" #include "rendercache.h" -#include "stillimagecache.h" #include "threading/threadticket.h" namespace olive { @@ -33,7 +32,7 @@ namespace olive { class RenderProcessor : public NodeTraverser { public: - static void Process(RenderTicketPtr ticket, Renderer* render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader); + static void Process(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader); struct RenderedWaveform { const ClipBlock* block; @@ -45,24 +44,24 @@ public: protected: virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange &range) override; - virtual QVariant ProcessVideoFootage(const FootageJob &stream, const rational &input_time) override; + virtual TexturePtr ProcessVideoFootage(const FootageJob &stream, const rational &input_time) override; - virtual QVariant ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time) override; + virtual SampleBufferPtr ProcessAudioFootage(const FootageJob &stream, const TimeRange &input_time) override; - virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override; + virtual TexturePtr ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override; - virtual QVariant ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) override; + virtual SampleBufferPtr ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) override; - virtual QVariant ProcessFrameGeneration(const Node *node, const GenerateJob& job) override; + virtual TexturePtr ProcessFrameGeneration(const Node *node, const GenerateJob& job) override; virtual bool CanCacheFrames() override; - virtual QVariant GetCachedTexture(const QByteArray &hash) override; + virtual TexturePtr GetCachedTexture(const QByteArray &hash) override; - virtual void SaveCachedTexture(const QByteArray& hash, const QVariant& texture) override; + virtual void SaveCachedTexture(const QByteArray& hash, TexturePtr texture) override; private: - RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, StillImageCache* still_image_cache, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader); + RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader); TexturePtr GenerateTexture(const rational& time, const rational& frame_length); @@ -76,8 +75,6 @@ private: Renderer* render_ctx_; - StillImageCache* still_image_cache_; - DecoderCache* decoder_cache_; ShaderCache* shader_cache_; diff --git a/app/render/stillimagecache.h b/app/render/stillimagecache.h deleted file mode 100644 index e696589cd..000000000 --- a/app/render/stillimagecache.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef STILLIMAGECACHE_H -#define STILLIMAGECACHE_H - -#include -#include - -#include "codec/decoder.h" -#include "common/rational.h" -#include "node/project/footage/footage.h" -#include "render/texture.h" - -namespace olive { - -class StillImageCache -{ -public: - struct Entry { - Entry(TexturePtr t, const Decoder::CodecStream& s, const QString& cs, bool a, int d, const rational& i, bool w) - { - texture = t; - stream = s; - colorspace = cs; - alpha_is_associated = a; - divider = d; - time = i; - working = w; - } - - TexturePtr texture; - Decoder::CodecStream stream; - QString colorspace; - bool alpha_is_associated; - int divider; - rational time; - bool working; - }; - - using EntryPtr = std::shared_ptr; - - QMutex* mutex() - { - return &mutex_; - } - - QWaitCondition* wait_cond() - { - return &wait_cond_; - } - - const QVector& entries() const - { - return entries_; - } - - static bool CompareEntryMetadata(EntryPtr a, EntryPtr b) - { - return (a->stream == b->stream - && a->colorspace == b->colorspace - && a->alpha_is_associated == b->alpha_is_associated - && a->divider == b->divider - && a->time == b->time); - } - - void PushEntry(EntryPtr e) - { - entries_.prepend(e); - - if (entries_.size() > 8) { - entries_.removeLast(); - } - } - -private: - QMutex mutex_; - - QWaitCondition wait_cond_; - - QVector entries_; - -}; - -} - -#endif // STILLIMAGECACHE_H diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 487decba2..92abfac72 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -91,7 +91,7 @@ bool RenderTask::Render(ColorManager* manager, } times[i] = r; - hashes[i] = RenderManager::instance()->Hash(viewer()->GetConnectedTextureOutput(), viewer()->GetValueHintForInput(ViewerOutput::kTextureInput), video_params_, r); + hashes[i] = RenderManager::instance()->Hash(viewer(), video_params_, r); } // Filter out duplicates