diff --git a/app/node/input.cpp b/app/node/input.cpp index b86dea364..eb9c79ee1 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -283,7 +283,7 @@ void NodeInput::set_maximum(const QVariant &max) has_maximum_ = true; } -void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_connections) +void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_connections, bool lock_connections) { Q_ASSERT(source->id() == dest->id()); @@ -295,7 +295,7 @@ void NodeInput::CopyValues(NodeInput *source, NodeInput *dest, bool include_conn // Copy connections if (include_connections && source->get_connected_output() != nullptr) { - ConnectEdge(source->get_connected_output(), dest); + ConnectEdge(source->get_connected_output(), dest, lock_connections); } // If these inputs are an array, copy the subparams too diff --git a/app/node/input.h b/app/node/input.h index a9db08aa6..4885833a4 100644 --- a/app/node/input.h +++ b/app/node/input.h @@ -121,7 +121,7 @@ public: /** * @brief Copy all values including keyframe information and connections from another NodeInput */ - static void CopyValues(NodeInput* source, NodeInput* dest, bool include_connections = true); + static void CopyValues(NodeInput* source, NodeInput* dest, bool include_connections = true, bool lock_connections = true); signals: void ValueChanged(const rational& start, const rational& end); diff --git a/app/node/node.cpp b/app/node/node.cpp index 527e5f2e1..cec2712fa 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -182,7 +182,7 @@ void Node::CopyInputs(Node *source, Node *destination, bool include_connections) if (src->dependent()) { NodeInput* dst = static_cast(dst_param.at(i)); - NodeInput::CopyValues(src, dst, include_connections); + NodeInput::CopyValues(src, dst, include_connections, false); } } } diff --git a/app/node/param.cpp b/app/node/param.cpp index 359d29bf9..09a9d07d0 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -103,7 +103,7 @@ void NodeParam::DisconnectAll() } } -NodeEdgePtr NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input) +NodeEdgePtr NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input, bool lock) { // If the input can only accept one input (the default) and has one already, disconnect it DisconnectForNewOutput(input); @@ -127,14 +127,18 @@ NodeEdgePtr NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input) // that's difficult to diagnose. This makes that issue very clear. Q_ASSERT(output->parentNode() != input->parentNode()); - output->parentNode()->LockUserInput(); - input->parentNode()->LockUserInput(); + if (lock) { + output->parentNode()->LockUserInput(); + input->parentNode()->LockUserInput(); + } output->edges_.append(edge); input->edges_.append(edge); - output->parentNode()->UnlockUserInput(); - input->parentNode()->UnlockUserInput(); + if (lock) { + output->parentNode()->UnlockUserInput(); + input->parentNode()->UnlockUserInput(); + } // Emit a signal than an edge was added (only one signal needs emitting) emit input->EdgeAdded(edge); diff --git a/app/node/param.h b/app/node/param.h index a0ca4e665..f7ac48dd4 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -292,7 +292,7 @@ public: * * This function emits EdgeAdded(). */ - static NodeEdgePtr ConnectEdge(NodeOutput *output, NodeInput *input); + static NodeEdgePtr ConnectEdge(NodeOutput *output, NodeInput *input, bool lock = true); /** * @brief Disconnect an edge diff --git a/app/project/item/footage/footage.cpp b/app/project/item/footage/footage.cpp index b82bc16ce..ec33d94dc 100644 --- a/app/project/item/footage/footage.cpp +++ b/app/project/item/footage/footage.cpp @@ -35,7 +35,7 @@ Footage::~Footage() ClearStreams(); } -const Footage::Status& Footage::status() +const Footage::Status& Footage::status() const { return status_; } @@ -56,7 +56,7 @@ void Footage::Clear() set_status(kUnprobed); } -const QString &Footage::filename() +const QString &Footage::filename() const { return filename_; } @@ -66,7 +66,7 @@ void Footage::set_filename(const QString &s) filename_ = s; } -const QDateTime &Footage::timestamp() +const QDateTime &Footage::timestamp() const { return timestamp_; } @@ -85,17 +85,17 @@ void Footage::add_stream(StreamPtr s) streams_.last()->set_footage(this); } -StreamPtr Footage::stream(int index) +StreamPtr Footage::stream(int index) const { return streams_.at(index); } -const QList &Footage::streams() +const QList &Footage::streams() const { return streams_; } -int Footage::stream_count() +int Footage::stream_count() const { return streams_.size(); } @@ -105,7 +105,7 @@ Item::Type Footage::type() const return kFootage; } -const QString &Footage::decoder() +const QString &Footage::decoder() const { return decoder_; } @@ -139,7 +139,7 @@ QIcon Footage::icon() return olive::icon::Image; } - /* fall through */ + /* fall-through */ case kInvalid: return olive::icon::Error; } diff --git a/app/project/item/footage/footage.h b/app/project/item/footage/footage.h index 53775a467..2059e0544 100644 --- a/app/project/item/footage/footage.h +++ b/app/project/item/footage/footage.h @@ -24,6 +24,7 @@ #include #include +#include "common/constructors.h" #include "common/rational.h" #include "project/item/item.h" #include "project/item/footage/audiostream.h" @@ -59,25 +60,7 @@ public: */ virtual ~Footage() override; - /** - * @brief Deleted copy constructor - */ - Footage(const Footage& other) = delete; - - /** - * @brief Deleted move constructor - */ - Footage(Footage&& other) = delete; - - /** - * @brief Deleted copy assignment - */ - Footage& operator=(const Footage& other) = delete; - - /** - * @brief Deleted move assignment - */ - Footage& operator=(Footage&& other) = delete; + DISABLE_COPY_MOVE(Footage) /** * @brief Check the ready state of this Footage object @@ -86,7 +69,7 @@ public: * * If the Footage has been successfully probed, this will return TRUE. */ - const Status& status(); + const Status& status() const; /** * @brief Set ready state @@ -114,7 +97,7 @@ public: /** * @brief Return the current filename of this Footage object */ - const QString& filename(); + const QString& filename() const; /** * @brief Set the filename @@ -134,7 +117,7 @@ public: * The file's last modified timestamp is stored for potential organization in the ProjectExplorer. It can be * retrieved here. */ - const QDateTime& timestamp(); + const QDateTime& timestamp() const; /** * @brief Set the last modified time/date @@ -172,17 +155,17 @@ public: * * The stream at the index provided */ - StreamPtr stream(int index); + StreamPtr stream(int index) const; /** * @brief Returns a list of the streams in this Footage */ - const QList& streams(); + const QList& streams() const; /** * @brief Retrieve total number of streams in this Footage file */ - int stream_count(); + int stream_count() const; /** * @brief Item::Type() override @@ -198,7 +181,7 @@ public: * * A decoder ID */ - const QString& decoder(); + const QString& decoder() const; /** * @brief Used by decoders when they Probe to attach itself to this Footage diff --git a/app/project/item/footage/stream.cpp b/app/project/item/footage/stream.cpp index eb364d1f0..3ce60ecc6 100644 --- a/app/project/item/footage/stream.cpp +++ b/app/project/item/footage/stream.cpp @@ -20,6 +20,7 @@ #include "stream.h" +#include "footage.h" #include "ui/icons/icons.h" Stream::Stream() : @@ -114,3 +115,14 @@ QIcon Stream::IconFromType(const Stream::Type &type) return QIcon(); } + +StreamID Stream::ToID() const +{ + return StreamID(footage_->filename(), index_); +} + +StreamID::StreamID(const QString &filename, const int &stream_index) : + filename_(filename), + stream_index_(stream_index) +{ +} diff --git a/app/project/item/footage/stream.h b/app/project/item/footage/stream.h index 2fcca5075..80e555133 100644 --- a/app/project/item/footage/stream.h +++ b/app/project/item/footage/stream.h @@ -29,6 +29,17 @@ class Footage; +class StreamID { +public: + StreamID(const QString& filename, const int& stream_index); + +private: + QString filename_; + + int stream_index_; + +}; + /** * @brief A base class for keeping metadata about a media stream. * @@ -84,6 +95,8 @@ public: static QIcon IconFromType(const Type& type); + StreamID ToID() const; + private: Footage* footage_; diff --git a/app/render/backend/CMakeLists.txt b/app/render/backend/CMakeLists.txt index 663d5c548..4df8c6e0c 100644 --- a/app/render/backend/CMakeLists.txt +++ b/app/render/backend/CMakeLists.txt @@ -21,9 +21,6 @@ add_subdirectory(vulkan) set(OLIVE_SOURCES ${OLIVE_SOURCES} - render/backend/decodercache.h - render/backend/decodercache.cpp - render/backend/renderbackend.h render/backend/renderbackend.cpp render/backend/renderworker.h @@ -40,6 +37,10 @@ set(OLIVE_SOURCES render/backend/videorenderframecache.cpp render/backend/videorenderworker.h render/backend/videorenderworker.cpp + + render/backend/rendercache.h + render/backend/colorprocessorcache.h + render/backend/decodercache.h PARENT_SCOPE ) diff --git a/app/render/backend/audio/audiobackend.cpp b/app/render/backend/audio/audiobackend.cpp index 82647ab66..19c0cbce2 100644 --- a/app/render/backend/audio/audiobackend.cpp +++ b/app/render/backend/audio/audiobackend.cpp @@ -37,7 +37,7 @@ bool AudioBackend::InitInternal() // Initiate one thread per CPU core for (int i=0;iSetParameters(params()); processors_.append(processor); } @@ -67,7 +67,7 @@ void AudioBackend::ConnectWorkerToThis(RenderWorker *worker) void AudioBackend::ThreadCompletedCache(NodeDependency dep, NodeValueTable data) { - caching_ = false; + SetWorkerBusyState(static_cast(sender()), false); QByteArray cached_samples = data.Get(NodeParam::kSamples).toByteArray(); diff --git a/app/render/backend/audio/audioworker.cpp b/app/render/backend/audio/audioworker.cpp index 70a21836f..c012976af 100644 --- a/app/render/backend/audio/audioworker.cpp +++ b/app/render/backend/audio/audioworker.cpp @@ -1,11 +1,13 @@ #include "audioworker.h" -AudioWorker::AudioWorker(DecoderCache *decoder_cache, QObject *parent) : - AudioRenderWorker(decoder_cache, parent) +AudioWorker::AudioWorker(QObject *parent) : + AudioRenderWorker(parent) { } -void AudioWorker::FrameToValue(FramePtr frame, NodeValueTable *table) +void AudioWorker::FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable *table) { + Q_UNUSED(stream) + table->Push(NodeParam::kSamples, frame->ToByteArray()); } diff --git a/app/render/backend/audio/audioworker.h b/app/render/backend/audio/audioworker.h index a9598d06b..3fdcb02ce 100644 --- a/app/render/backend/audio/audioworker.h +++ b/app/render/backend/audio/audioworker.h @@ -6,10 +6,10 @@ class AudioWorker : public AudioRenderWorker { public: - AudioWorker(DecoderCache* decoder_cache, QObject* parent = nullptr); + AudioWorker(QObject* parent = nullptr); protected: - virtual void FrameToValue(FramePtr frame, NodeValueTable* table) override; + virtual void FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable* table) override; private: diff --git a/app/render/backend/audiorenderbackend.cpp b/app/render/backend/audiorenderbackend.cpp index 1b49e9b37..ba0b0dd07 100644 --- a/app/render/backend/audiorenderbackend.cpp +++ b/app/render/backend/audiorenderbackend.cpp @@ -29,6 +29,8 @@ void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms) void AudioRenderBackend::InvalidateCache(const rational &start_range, const rational &end_range) { + RenderBackend::InvalidateCache(start_range, end_range); + rational start_range_adj = qMax(rational(0), start_range); rational end_range_adj = qMin(SequenceLength(), end_range); diff --git a/app/render/backend/audiorenderworker.cpp b/app/render/backend/audiorenderworker.cpp index 61196027b..438b62fd7 100644 --- a/app/render/backend/audiorenderworker.cpp +++ b/app/render/backend/audiorenderworker.cpp @@ -2,8 +2,8 @@ #include "audio/audiomanager.h" -AudioRenderWorker::AudioRenderWorker(DecoderCache *decoder_cache, QObject *parent) : - RenderWorker(decoder_cache, parent) +AudioRenderWorker::AudioRenderWorker(QObject *parent) : + RenderWorker(parent) { } diff --git a/app/render/backend/audiorenderworker.h b/app/render/backend/audiorenderworker.h index 9aea9c154..4a2fae507 100644 --- a/app/render/backend/audiorenderworker.h +++ b/app/render/backend/audiorenderworker.h @@ -7,7 +7,7 @@ class AudioRenderWorker : public RenderWorker { Q_OBJECT public: - AudioRenderWorker(DecoderCache* decoder_cache, QObject* parent = nullptr); + AudioRenderWorker(QObject* parent = nullptr); void SetParameters(const AudioRenderingParams& audio_params); diff --git a/app/render/backend/colorprocessorcache.h b/app/render/backend/colorprocessorcache.h new file mode 100644 index 000000000..70512d02e --- /dev/null +++ b/app/render/backend/colorprocessorcache.h @@ -0,0 +1,10 @@ +#ifndef COLORPROCESSORCACHE_H +#define COLORPROCESSORCACHE_H + +#include "project/item/footage/stream.h" +#include "render/colorprocessor.h" +#include "rendercache.h" + +using ColorProcessorCache = RenderCache; + +#endif // COLORPROCESSORCACHE_H diff --git a/app/render/backend/decodercache.cpp b/app/render/backend/decodercache.cpp deleted file mode 100644 index 29939c289..000000000 --- a/app/render/backend/decodercache.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "decodercache.h" - -DecoderCache::DecoderCache() -{ - -} - -void DecoderCache::Clear() -{ - decoders_.clear(); -} - -void DecoderCache::AddDecoder(Stream *stream, DecoderPtr shader) -{ - decoders_.insert(stream, shader); -} - -DecoderPtr DecoderCache::GetDecoder(Stream *stream) -{ - return decoders_.value(stream); -} diff --git a/app/render/backend/decodercache.h b/app/render/backend/decodercache.h index 47011e965..c2a2adee0 100644 --- a/app/render/backend/decodercache.h +++ b/app/render/backend/decodercache.h @@ -3,24 +3,8 @@ #include "decoder/decoder.h" #include "project/item/footage/stream.h" +#include "rendercache.h" -/** - * @brief Thread-safe cache of decoders - */ -class DecoderCache -{ -public: - DecoderCache(); - - void Clear(); - - void AddDecoder(Stream* stream, DecoderPtr shader); - - DecoderPtr GetDecoder(Stream* stream); - -private: - QMap decoders_; - -}; +using DecoderCache = ThreadSafeRenderCache; #endif // DECODERCACHE_H diff --git a/app/render/backend/opengl/CMakeLists.txt b/app/render/backend/opengl/CMakeLists.txt index 9bc6fd91e..0b292eadd 100644 --- a/app/render/backend/opengl/CMakeLists.txt +++ b/app/render/backend/opengl/CMakeLists.txt @@ -20,12 +20,13 @@ set(OLIVE_SOURCES render/backend/opengl/functions.cpp render/backend/opengl/openglbackend.h render/backend/opengl/openglbackend.cpp + render/backend/opengl/openglcolorprocessor.h + render/backend/opengl/openglcolorprocessor.cpp render/backend/opengl/openglframebuffer.h render/backend/opengl/openglframebuffer.cpp render/backend/opengl/openglshader.h render/backend/opengl/openglshader.cpp render/backend/opengl/openglshadercache.h - render/backend/opengl/openglshadercache.cpp render/backend/opengl/opengltexture.h render/backend/opengl/opengltexture.cpp render/backend/opengl/openglworker.h diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index 63b25a0ee..120c610f5 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -6,7 +6,8 @@ #include "functions.h" OpenGLBackend::OpenGLBackend(QObject *parent) : - VideoRenderBackend(parent) + VideoRenderBackend(parent), + last_download_thread_(0) { } @@ -31,7 +32,7 @@ bool OpenGLBackend::InitInternal() // Initiate one thread per CPU core for (int i=0;iSetParameters(params()); processors_.append(processor); } @@ -76,14 +77,14 @@ bool OpenGLBackend::CompileInternal() foreach (Node* n, nodes) { // Check if we have a shader or not - if (!shader_cache_.HasShader(n)) { + if (!shader_cache_.Has(n->id())) { // Since we don't have a shader, compile one now QString node_code = n->Code(); // If the node has no code, it mustn't be GPU accelerated if (node_code.isEmpty()) { // We enter a null shader so we don't try to compile this again - shader_cache_.AddShader(n, nullptr); + shader_cache_.Add(n->id(), nullptr); } else { // Since we have shader code, compile it now OpenGLShaderPtr program; @@ -113,7 +114,7 @@ bool OpenGLBackend::CompileInternal() return false; } - shader_cache_.AddShader(n, program); + shader_cache_.Add(n->id(), program); //qDebug() << "Compiled" << connected_output->parent()->id() << "->" << connected_output->id(); } @@ -135,7 +136,7 @@ bool OpenGLBackend::TimeIsCached(const TimeRange &time) void OpenGLBackend::ThreadCompletedFrame(NodeDependency path, QByteArray hash, NodeValueTable table) { - caching_ = false; + SetWorkerBusyState(static_cast(sender()), false); QVariant value = table.Get(NodeParam::kTexture); OpenGLTexturePtr texture = value.value(); @@ -148,19 +149,14 @@ void OpenGLBackend::ThreadCompletedFrame(NodeDependency path, QByteArray hash, N QString cache_fn = frame_cache()->CachePathName(hash); // Find an available worker to download this texture - foreach (RenderWorker* worker, processors_) { - // Check if one is available, but worst case if none of them are available, just queue it on the last worker since - // it's the least likely to get work - if (worker->IsAvailable() || worker == processors_.last()) { - QMetaObject::invokeMethod(worker, - "Download", - Q_ARG(NodeDependency, path), - Q_ARG(QByteArray, hash), - Q_ARG(QVariant, QVariant::fromValue(texture)), - Q_ARG(QString, cache_fn)); - break; - } - } + QMetaObject::invokeMethod(processors_.at(last_download_thread_%processors_.size()), + "Download", + Q_ARG(NodeDependency, path), + Q_ARG(QByteArray, hash), + Q_ARG(QVariant, QVariant::fromValue(texture)), + Q_ARG(QString, cache_fn)); + + last_download_thread_++; } // Set as push texture @@ -168,6 +164,7 @@ void OpenGLBackend::ThreadCompletedFrame(NodeDependency path, QByteArray hash, N emit CachedFrameReady(path.in(), value); } + // Queue up a new frame for this worker CacheNext(); } @@ -175,18 +172,27 @@ void OpenGLBackend::ThreadCompletedDownload(NodeDependency dep, QByteArray hash) { frame_cache()->SetHash(dep.in(), hash); - emit CachedTimeReady(dep.in()); + // Emit for each frame that has this hash (some may have been added in ThreadSkippedFrame) + QList times_with_this_hash = frame_cache()->TimesWithHash(hash); + foreach (const rational& t, times_with_this_hash) { + emit CachedTimeReady(t); + } } -void OpenGLBackend::ThreadSkippedFrame() +void OpenGLBackend::ThreadSkippedFrame(NodeDependency dep, QByteArray hash) { - caching_ = false; + frame_cache()->SetHash(dep.in(), hash); + SetWorkerBusyState(static_cast(sender()), false); + + // Queue up a new frame for this worker CacheNext(); } void OpenGLBackend::ThreadHashAlreadyExists(NodeDependency dep, QByteArray hash) { ThreadCompletedDownload(dep, hash); + SetWorkerBusyState(static_cast(sender()), false); - ThreadSkippedFrame(); + // Queue up a new frame for this worker + CacheNext(); } diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/backend/opengl/openglbackend.h index acb8a807f..e05303d74 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/backend/opengl/openglbackend.h @@ -34,10 +34,12 @@ private: OpenGLShaderCache shader_cache_; + int last_download_thread_; + private slots: void ThreadCompletedFrame(NodeDependency path, QByteArray hash, NodeValueTable table); void ThreadCompletedDownload(NodeDependency dep, QByteArray hash); - void ThreadSkippedFrame(); + void ThreadSkippedFrame(NodeDependency dep, QByteArray hash); void ThreadHashAlreadyExists(NodeDependency dep, QByteArray hash); }; diff --git a/app/render/backend/opengl/openglcolorprocessor.cpp b/app/render/backend/opengl/openglcolorprocessor.cpp new file mode 100644 index 000000000..052f301b1 --- /dev/null +++ b/app/render/backend/opengl/openglcolorprocessor.cpp @@ -0,0 +1,66 @@ +#include "openglcolorprocessor.h" + +#include +#include + +#include "functions.h" + +void OpenGLColorProcessor::Enable(QOpenGLContext *context, bool alpha_is_associated) +{ + if (IsEnabled()) { + return; + } + + context_ = context; + + pipeline_ = OpenGLShader::CreateOCIO(context_, + ocio_lut_, + GetProcessor(), + alpha_is_associated); +} + +bool OpenGLColorProcessor::IsEnabled() const +{ + return ocio_lut_; +} + +OpenGLShaderPtr OpenGLColorProcessor::pipeline() const +{ + return pipeline_; +} + +void OpenGLColorProcessor::ProcessOpenGL() +{ + olive::gl::OCIOBlit(pipeline_, ocio_lut_); +} + +OpenGLColorProcessor::OpenGLColorProcessor(const QString &source_space, const QString &dest_space) : + ColorProcessor(source_space, dest_space), + ocio_lut_(0) +{ +} + +OpenGLColorProcessor::OpenGLColorProcessor(const QString &source_space, QString display, QString view, const QString &look) : + ColorProcessor(source_space, display, view, look), + ocio_lut_(0) +{ +} + +OpenGLColorProcessor::~OpenGLColorProcessor() +{ + if (IsEnabled()) { + // Clean up OCIO LUT texture and shader + context_->functions()->glDeleteTextures(1, &ocio_lut_); + pipeline_ = nullptr; + } +} + +OpenGLColorProcessorPtr OpenGLColorProcessor::CreateOpenGL(const QString &source_space, const QString &dest_space) +{ + return std::make_shared(source_space, dest_space); +} + +OpenGLColorProcessorPtr OpenGLColorProcessor::CreateOpenGL(const QString &source_space, const QString &display, const QString &view, const QString &look) +{ + return std::make_shared(source_space, display, view, look); +} diff --git a/app/render/backend/opengl/openglcolorprocessor.h b/app/render/backend/opengl/openglcolorprocessor.h new file mode 100644 index 000000000..9ab7d955f --- /dev/null +++ b/app/render/backend/opengl/openglcolorprocessor.h @@ -0,0 +1,45 @@ +#ifndef OPENGLCOLORPROCESSOR_H +#define OPENGLCOLORPROCESSOR_H + +#include "openglshader.h" +#include "render/colorprocessor.h" + +class OpenGLColorProcessor; +using OpenGLColorProcessorPtr = std::shared_ptr; + +class OpenGLColorProcessor : public ColorProcessor +{ +public: + OpenGLColorProcessor(const QString &source_space, const QString &dest_space); + + OpenGLColorProcessor(const QString& source_space, + QString display, + QString view, + const QString& look); + + ~OpenGLColorProcessor(); + + static OpenGLColorProcessorPtr CreateOpenGL(const QString& source_space, const QString& dest_space); + + static OpenGLColorProcessorPtr CreateOpenGL(const QString& source_space, + const QString& display, + const QString& view, + const QString& look); + + void Enable(QOpenGLContext* context, bool alpha_is_associated); + bool IsEnabled() const; + + OpenGLShaderPtr pipeline() const; + + void ProcessOpenGL(); + +private: + QOpenGLContext* context_; + + GLuint ocio_lut_; + + OpenGLShaderPtr pipeline_; + +}; + +#endif // OPENGLCOLORPROCESSOR_H diff --git a/app/render/backend/opengl/openglshadercache.cpp b/app/render/backend/opengl/openglshadercache.cpp deleted file mode 100644 index e2f3d5cf9..000000000 --- a/app/render/backend/opengl/openglshadercache.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "openglshadercache.h" - -#include "node/node.h" - -void OpenGLShaderCache::Clear() -{ - compiled_nodes_.clear(); -} - -void OpenGLShaderCache::AddShader(Node *output, OpenGLShaderPtr shader) -{ - compiled_nodes_.insert(GenerateShaderID(output), shader); -} - -OpenGLShaderPtr OpenGLShaderCache::GetShader(Node *output) -{ - return compiled_nodes_.value(GenerateShaderID(output)); -} - -QString OpenGLShaderCache::GenerateShaderID(Node *output) -{ - return output->id(); -} - -bool OpenGLShaderCache::HasShader(Node *output) -{ - return compiled_nodes_.contains(GenerateShaderID(output)); -} diff --git a/app/render/backend/opengl/openglshadercache.h b/app/render/backend/opengl/openglshadercache.h index 8626f31b2..3ccf6e8dc 100644 --- a/app/render/backend/opengl/openglshadercache.h +++ b/app/render/backend/opengl/openglshadercache.h @@ -1,32 +1,11 @@ #ifndef OPENGLSHADERCACHE_H #define OPENGLSHADERCACHE_H -#include +#include -#include "node/output.h" #include "openglshader.h" +#include "render/backend/rendercache.h" -/** - * @brief Thread-safe cache of OpenGL shaders - */ -class OpenGLShaderCache -{ -public: - OpenGLShaderCache() = default; - - void Clear(); - - void AddShader(Node* output, OpenGLShaderPtr shader); - - OpenGLShaderPtr GetShader(Node* output); - - bool HasShader(Node* output); - -private: - QString GenerateShaderID(Node *output); - - QMap compiled_nodes_; - -}; +using OpenGLShaderCache = RenderCache; #endif // OPENGLSHADERCACHE_H diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index 9f95b0abd..82ff47595 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -1,11 +1,14 @@ #include "openglworker.h" +#include "core.h" #include "functions.h" #include "node/node.h" +#include "openglcolorprocessor.h" +#include "render/colormanager.h" #include "render/pixelservice.h" -OpenGLWorker::OpenGLWorker(QOpenGLContext *share_ctx, OpenGLShaderCache *shader_cache, DecoderCache *decoder_cache, VideoRenderFrameCache *frame_cache, QObject *parent) : - VideoRenderWorker(decoder_cache, frame_cache, parent), +OpenGLWorker::OpenGLWorker(QOpenGLContext *share_ctx, OpenGLShaderCache *shader_cache, VideoRenderFrameCache *frame_cache, QObject *parent) : + VideoRenderWorker(frame_cache, parent), share_ctx_(share_ctx), ctx_(nullptr), functions_(nullptr), @@ -47,18 +50,79 @@ bool OpenGLWorker::InitInternal() return true; } -void OpenGLWorker::FrameToValue(FramePtr frame, NodeValueTable *table) +void OpenGLWorker::FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable *table) { + // Set up OCIO context + OpenGLColorProcessorPtr color_processor = std::static_pointer_cast(color_cache()->Get(stream.get())); + + // Ensure stream is video or image type + if (stream->type() != Stream::kVideo && stream->type() != Stream::kImage) { + return; + } + + ImageStreamPtr video_stream = std::static_pointer_cast(stream); + + if (!color_processor) { + QString input_colorspace = video_stream->colorspace(); + if (input_colorspace.isEmpty()) { + // FIXME: Should use Stream->Footage to find the Project* since that's a direct chain + input_colorspace = olive::core.GetActiveProject()->default_input_colorspace(); + } + + color_processor = OpenGLColorProcessor::CreateOpenGL(input_colorspace, + OCIO::ROLE_SCENE_LINEAR); + color_cache()->Add(stream.get(), color_processor); + } + + // OCIO's CPU conversion is more accurate, so for online we render on CPU but offline we render GPU + if (video_params().mode() == olive::kOnline) { + // If alpha is associated, disassociate for the color transform + if (video_stream->premultiplied_alpha()) { + ColorManager::DisassociateAlpha(frame); + } + + // Convert frame to float for OCIO + frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA32F); + + // Perform color transform + color_processor->ConvertFrame(frame); + + // Associate alpha + if (video_stream->premultiplied_alpha()) { + ColorManager::ReassociateAlpha(frame); + } else { + ColorManager::AssociateAlpha(frame); + } + } + OpenGLTexturePtr footage_tex = std::make_shared(); footage_tex->Create(ctx_, frame); - // OCIO's CPU conversion is more accurate, so for online we render on CPU but offline we render GPU - //if (video_params().mode() == olive::kOnline) { - // Convert frame to float for - //frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA32F); - //} + if (video_params().mode() == olive::kOffline) { + if (!color_processor->IsEnabled()) { + color_processor->Enable(ctx_, video_stream->premultiplied_alpha()); + } - // FIXME: Alpha association and color management + // Create destination texture + OpenGLTexturePtr associated_tex = std::make_shared(); + associated_tex->Create(ctx_, footage_tex->width(), footage_tex->height(), footage_tex->format()); + + // Set viewport for texture size + functions_->glViewport(0, 0, footage_tex->width(), footage_tex->height()); + + buffer_.Attach(associated_tex); + buffer_.Bind(); + footage_tex->Bind(); + + // Blit old texture to new texture through OCIO shader + color_processor->ProcessOpenGL(); + + footage_tex->Release(); + buffer_.Release(); + buffer_.Detach(); + + footage_tex = associated_tex; + } table->Push(NodeParam::kTexture, QVariant::fromValue(footage_tex)); } @@ -66,7 +130,6 @@ void OpenGLWorker::FrameToValue(FramePtr frame, NodeValueTable *table) void OpenGLWorker::CloseInternal() { buffer_.Destroy(); - functions_ = nullptr; delete ctx_; } @@ -80,7 +143,7 @@ void OpenGLWorker::ParametersChangedEvent() void OpenGLWorker::RunNodeAccelerated(Node *node, const NodeValueDatabase *input_params, NodeValueTable *output_params) { - OpenGLShaderPtr shader = shader_cache_->GetShader(node); + OpenGLShaderPtr shader = shader_cache_->Get(node->id()); if (!shader) { return; @@ -184,7 +247,10 @@ void OpenGLWorker::RunNodeAccelerated(Node *node, const NodeValueDatabase *input } } - //qDebug() << " Blitting with shader!"; + // Ensure viewport is correct + functions_->glViewport(0, 0, video_params().effective_width(), video_params().effective_height()); + + // Blit this texture through this shader olive::gl::Blit(shader); // Release any textures we bound before diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h index a509ed018..f0bba8c76 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/backend/opengl/openglworker.h @@ -13,7 +13,6 @@ class OpenGLWorker : public VideoRenderWorker { public: OpenGLWorker(QOpenGLContext* share_ctx, OpenGLShaderCache* shader_cache, - DecoderCache* decoder_cache, VideoRenderFrameCache* frame_cache, QObject* parent = nullptr); @@ -45,7 +44,7 @@ protected: virtual void CloseInternal() override; - virtual void FrameToValue(FramePtr frame, NodeValueTable* table) override; + virtual void FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable* table) override; virtual void RunNodeAccelerated(Node *node, const NodeValueDatabase *input_params, NodeValueTable* output_params) override; diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 4ef03b849..63e6e4687 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -6,12 +6,12 @@ RenderBackend::RenderBackend(QObject *parent) : QObject(parent), compiled_(false), - caching_(false), started_(false), viewer_node_(nullptr), copied_viewer_node_(nullptr), value_update_queued_(false), - recompile_queued_(false) + recompile_queued_(false), + input_update_queued_(false) { } @@ -53,17 +53,22 @@ void RenderBackend::Close() CloseInternal(); - decoder_cache_.Clear(); + for (int i=0;iquit(); - thread->wait(); // FIXME: Maximum time in case a thread is stuck? + threads_.at(i)->quit(); } + + for (int i=0;iwait(); // FIXME: Maximum time in case a thread is stuck? + delete threads_.at(i); + delete processors_.at(i); + } + threads_.clear(); - - foreach (RenderWorker* processor, processors_) { - delete processor; - } processors_.clear(); } @@ -100,12 +105,17 @@ bool RenderBackend::IsInitiated() return started_; } +void RenderBackend::InvalidateCache(const rational &start_range, const rational &end_range) +{ + Q_UNUSED(start_range) + Q_UNUSED(end_range) + + input_update_queued_ = true; +} + bool RenderBackend::Compile() { - if (recompile_queued_) { - Decompile(); - recompile_queued_ = false; - } else if (compiled_) { + if (compiled_) { return true; } @@ -199,39 +209,50 @@ void RenderBackend::DisconnectViewer(ViewerOutput *node) void RenderBackend::CacheNext() { - if (!Init() || cache_queue_.isEmpty() || !ViewerIsConnected() || caching_) { + if (!Init() + || cache_queue_.isEmpty() + || !ViewerIsConnected()) { return; } - UpdateNodeInputs(); - - TimeRange cache_frame = cache_queue_.takeFirst(); - - //qDebug() << "Caching" << cache_frame.in(); - - caching_ = GenerateData(cache_frame); -} - -bool RenderBackend::GenerateData(const TimeRange &range) -{ - if (!Compile()) { - qDebug() << "Graph remains uncompiled, nothing to be done"; - return false; + if ((input_update_queued_ || recompile_queued_) && !AllProcessorsAreAvailable()) { + qDebug() << "Blocking render while we wait for all workers to finish..."; + return; } - NodeDependency dep = NodeDependency(GetDependentInput()->get_connected_node(), range.in(), range.out()); + if (input_update_queued_) { + UpdateNodeInputs(); + input_update_queued_ = false; + } + + if (recompile_queued_) { + Decompile(); + recompile_queued_ = false; + } + + if (!compiled_ && !Compile()) { + qDebug() << "Graph remains uncompiled, nothing to be done"; + return; + } foreach (RenderWorker* worker, processors_) { - if (worker->IsAvailable() || worker == processors_.last()) { + if (cache_queue_.isEmpty()) { + break; + } + + if (!WorkerIsBusy(worker)) { + TimeRange cache_frame = cache_queue_.takeFirst(); + + NodeDependency dep = NodeDependency(GetDependentInput()->get_connected_node(), cache_frame.in(), cache_frame.out()); + QMetaObject::invokeMethod(worker, "Render", Qt::QueuedConnection, Q_ARG(NodeDependency, dep)); - return true; + + SetWorkerBusyState(worker, true); } } - - return false; } ViewerOutput *RenderBackend::viewer_node() const @@ -244,11 +265,6 @@ bool RenderBackend::ViewerIsConnected() const return viewer_node_ != nullptr; } -DecoderCache *RenderBackend::decoder_cache() -{ - return &decoder_cache_; -} - const QString &RenderBackend::cache_id() const { return cache_id_; @@ -274,6 +290,27 @@ void RenderBackend::UpdateNodeInputs() } } +bool RenderBackend::WorkerIsBusy(RenderWorker *worker) const +{ + return processor_busy_state_.at(processors_.indexOf(worker)); +} + +void RenderBackend::SetWorkerBusyState(RenderWorker *worker, bool busy) +{ + processor_busy_state_.replace(processors_.indexOf(worker), busy); +} + +bool RenderBackend::AllProcessorsAreAvailable() const +{ + foreach (bool busy, processor_busy_state_) { + if (busy) { + return false; + } + } + + return true; +} + const QVector &RenderBackend::threads() { return threads_; @@ -291,7 +328,6 @@ void RenderBackend::InitWorkers() QThread* thread = threads().at(i); // Connect to it - connect(processor, SIGNAL(RequestSibling(NodeDependency)), this, SLOT(ThreadRequestedSibling(NodeDependency))); ConnectWorkerToThis(processor); // Finally, we can move it to its own thread @@ -300,20 +336,9 @@ void RenderBackend::InitWorkers() // This function blocks the main thread intentionally. See the documentation for this function to see why. processor->Init(); } -} -void RenderBackend::ThreadRequestedSibling(NodeDependency dep) -{ - // Try to queue another thread to run this dep in advance - foreach (RenderWorker* worker, processors_) { - if (worker->IsAvailable()) { - QMetaObject::invokeMethod(worker, - "RenderAsSibling", - Qt::QueuedConnection, - Q_ARG(NodeDependency, dep)); - return; - } - } + processor_busy_state_.resize(processors_.size()); + processor_busy_state_.fill(false); } void RenderBackend::QueueRecompile() diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index 50eb5aa80..d9d913381 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -30,7 +30,7 @@ public: bool IsInitiated(); public slots: - virtual void InvalidateCache(const rational &start_range, const rational &end_range) = 0; + virtual void InvalidateCache(const rational &start_range, const rational &end_range); bool Compile(); @@ -70,8 +70,6 @@ protected: */ void CacheNext(); - bool GenerateData(const TimeRange& range); - void InitWorkers(); virtual NodeInput* GetDependentInput() = 0; @@ -82,23 +80,24 @@ protected: bool ViewerIsConnected() const; - DecoderCache* decoder_cache(); - const QString& cache_id() const; void QueueValueUpdate(const TimeRange& range); void UpdateNodeInputs(); + bool WorkerIsBusy(RenderWorker* worker) const; + void SetWorkerBusyState(RenderWorker* worker, bool busy); + QList cache_queue_; QVector processors_; bool compiled_; - bool caching_; - private: + bool AllProcessorsAreAvailable() const; + /** * @brief Internal list of RenderProcessThreads */ @@ -124,8 +123,6 @@ private: */ QString error_; - DecoderCache decoder_cache_; - QString cache_name_; qint64 cache_time_; QString cache_id_; @@ -137,10 +134,11 @@ private: TimeRange value_update_range_; bool recompile_queued_; + bool input_update_queued_; + + QVector processor_busy_state_; private slots: - void ThreadRequestedSibling(NodeDependency dep); - void QueueRecompile(); }; diff --git a/app/render/backend/rendercache.h b/app/render/backend/rendercache.h new file mode 100644 index 000000000..1a015168b --- /dev/null +++ b/app/render/backend/rendercache.h @@ -0,0 +1,64 @@ +#ifndef RENDERCACHE_H +#define RENDERCACHE_H + +#include +#include + +template +class RenderCache +{ +public: + RenderCache() = default; + + void Clear(){values_.clear();} + + void Add(K key, V val){values_.insert(key, val);} + + V Get(K key) const {return values_.value(key);} + + bool Has(K key) const {return values_.contains(key);} + +private: + QMap values_; +}; + +template +class ThreadSafeRenderCache +{ +public: + ThreadSafeRenderCache() = default; + + void Clear() { + lock_.lock(); + values_.clear(); + lock_.unlock(); + } + + void Add(K key, V val) { + lock_.lock(); + values_.insert(key, val); + lock_.unlock(); + } + + V Get(K key) { + lock_.lock(); + V val = values_.value(key); + lock_.unlock(); + return val; + } + + bool Has(K key) { + lock_.lock(); + bool has = values_.contains(key); + lock_.unlock(); + return has; + } + +private: + QMap values_; + + QMutex lock_; + +}; + +#endif // RENDERCACHE_H diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index c83c0ef71..49d02b8cd 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -4,19 +4,12 @@ #include "node/block/block.h" -RenderWorker::RenderWorker(DecoderCache *decoder_cache, QObject *parent) : +RenderWorker::RenderWorker(QObject *parent) : QObject(parent), - working_(0), - started_(false), - decoder_cache_(decoder_cache) + started_(false) { } -bool RenderWorker::IsAvailable() -{ - return (working_ == 0); -} - bool RenderWorker::Init() { if (started_) { @@ -34,6 +27,8 @@ void RenderWorker::Close() { CloseInternal(); + decoder_cache_.Clear(); + started_ = false; } @@ -50,9 +45,6 @@ NodeValueTable RenderWorker::RenderAsSibling(NodeDependency dep) //qDebug() << "Processing" << node->id(); - // Set working state - working_++; - // Firstly we check if this node is a "Block", if it is that means it's part of a linked list of mutually exclusive // nodes based on time and we might need to locate which Block to attach to if (node->IsTrack()) { @@ -64,17 +56,9 @@ NodeValueTable RenderWorker::RenderAsSibling(NodeDependency dep) // We're done! - // End this working state - working_--; - return value; } -DecoderCache *RenderWorker::decoder_cache() -{ - return decoder_cache_; -} - NodeValueTable RenderWorker::RenderInternal(const NodeDependency &path) { return RenderAsSibling(path); @@ -98,17 +82,16 @@ StreamPtr RenderWorker::ResolveStreamFromInput(NodeInput *input) return input->get_value_at_time(0).value(); } -DecoderPtr RenderWorker::ResolveDecoderFromInput(NodeInput *input) +DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream) { // Access a map of Node inputs and decoder instances and retrieve a frame! - StreamPtr stream = ResolveStreamFromInput(input); - DecoderPtr decoder = decoder_cache()->GetDecoder(stream.get()); + DecoderPtr decoder = decoder_cache_.Get(stream.get()); if (decoder == nullptr && stream != nullptr) { // Create a new Decoder here - DecoderPtr decoder = Decoder::CreateFromID(stream->footage()->decoder()); + decoder = Decoder::CreateFromID(stream->footage()->decoder()); decoder->set_stream(stream); - decoder_cache()->AddDecoder(stream.get(), decoder); + decoder_cache_.Add(stream.get(), decoder); } return decoder; @@ -146,13 +129,17 @@ NodeValueTable RenderWorker::ProcessNodeNormally(const NodeDependency& dep) // Exception for Footage types where we actually retrieve some Footage data from a decoder if (input->data_type() == NodeParam::kFootage) { - DecoderPtr decoder = ResolveDecoderFromInput(input); + StreamPtr stream = ResolveStreamFromInput(input); - if (decoder) { - FramePtr frame = RetrieveFromDecoder(decoder, input_time); + if (stream) { + DecoderPtr decoder = ResolveDecoderFromInput(stream); - if (frame) { - FrameToValue(frame, &table); + if (decoder) { + FramePtr frame = RetrieveFromDecoder(decoder, input_time); + + if (frame) { + FrameToValue(stream, frame, &table); + } } } } diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index c023d38eb..21530c069 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -12,7 +12,7 @@ class RenderWorker : public QObject { Q_OBJECT public: - RenderWorker(DecoderCache* decoder_cache, QObject* parent = nullptr); + RenderWorker(QObject* parent = nullptr); DISABLE_COPY_MOVE(RenderWorker) @@ -20,8 +20,6 @@ public: bool IsStarted(); - bool IsAvailable(); - public slots: void Close(); @@ -30,8 +28,6 @@ public slots: NodeValueTable RenderAsSibling(NodeDependency dep); signals: - void RequestSibling(NodeDependency path); - void CompletedCache(NodeDependency dep, NodeValueTable data); protected: @@ -46,24 +42,20 @@ protected: virtual void RunNodeAccelerated(Node *node, const NodeValueDatabase *input_params, NodeValueTable* output_params); StreamPtr ResolveStreamFromInput(NodeInput* input); - DecoderPtr ResolveDecoderFromInput(NodeInput* input); + DecoderPtr ResolveDecoderFromInput(StreamPtr stream); virtual FramePtr RetrieveFromDecoder(DecoderPtr decoder, const TimeRange& range) = 0; - virtual void FrameToValue(FramePtr frame, NodeValueTable* table) = 0; + virtual void FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable* table) = 0; NodeValueTable ProcessNodeNormally(const NodeDependency &dep); virtual NodeValueTable RenderBlock(TrackOutput *track, const TimeRange& range) = 0; - DecoderCache* decoder_cache(); - - QAtomicInt working_; - private: bool started_; - DecoderCache* decoder_cache_; + DecoderCache decoder_cache_; }; diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 2d803bcb9..3e54f4752 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -43,6 +43,8 @@ void VideoRenderBackend::InvalidateCache(const rational &start_range, const rati return; } + RenderBackend::InvalidateCache(start_range, end_range); + // Adjust range to min/max values rational start_range_adj = qMax(rational(0), start_range); rational end_range_adj = qMin(SequenceLength(), end_range); @@ -175,7 +177,7 @@ void VideoRenderBackend::CacheIDChangedEvent(const QString &id) void VideoRenderBackend::ConnectWorkerToThis(RenderWorker *processor) { connect(processor, SIGNAL(CompletedFrame(NodeDependency, QByteArray, NodeValueTable)), this, SLOT(ThreadCompletedFrame(NodeDependency, QByteArray, NodeValueTable))); - connect(processor, SIGNAL(HashAlreadyBeingCached()), this, SLOT(ThreadSkippedFrame())); + connect(processor, SIGNAL(HashAlreadyBeingCached(NodeDependency, QByteArray)), this, SLOT(ThreadSkippedFrame(NodeDependency, QByteArray))); connect(processor, SIGNAL(CompletedDownload(NodeDependency, QByteArray)), this, SLOT(ThreadCompletedDownload(NodeDependency, QByteArray))); connect(processor, SIGNAL(HashAlreadyExists(NodeDependency, QByteArray)), this, SLOT(ThreadHashAlreadyExists(NodeDependency, QByteArray))); } diff --git a/app/render/backend/videorenderbackend.h b/app/render/backend/videorenderbackend.h index c6875e1a9..975ab856c 100644 --- a/app/render/backend/videorenderbackend.h +++ b/app/render/backend/videorenderbackend.h @@ -23,6 +23,7 @@ #include +#include "colorprocessorcache.h" #include "node/output/viewer/viewer.h" #include "renderbackend.h" #include "render/pixelformat.h" diff --git a/app/render/backend/videorenderframecache.cpp b/app/render/backend/videorenderframecache.cpp index a6dc985e8..9de93623b 100644 --- a/app/render/backend/videorenderframecache.cpp +++ b/app/render/backend/videorenderframecache.cpp @@ -46,7 +46,7 @@ void VideoRenderFrameCache::SetCacheID(const QString &id) cache_id_ = id; } -QByteArray VideoRenderFrameCache::TimeToHash(const rational &time) +QByteArray VideoRenderFrameCache::TimeToHash(const rational &time) const { return time_hash_map_.value(time); } @@ -80,6 +80,21 @@ void VideoRenderFrameCache::Truncate(const rational &time) } } +QList VideoRenderFrameCache::TimesWithHash(const QByteArray &hash) +{ + QList list; + + QMap::const_iterator iterator; + + for (iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) { + if (iterator.value() == hash) { + list.append(iterator.key()); + } + } + + return list; +} + void VideoRenderFrameCache::RemoveHashFromCurrentlyCaching(const QByteArray &hash) { currently_caching_lock_.lock(); @@ -87,7 +102,7 @@ void VideoRenderFrameCache::RemoveHashFromCurrentlyCaching(const QByteArray &has currently_caching_lock_.unlock(); } -QString VideoRenderFrameCache::CachePathName(const QByteArray &hash) +QString VideoRenderFrameCache::CachePathName(const QByteArray &hash) const { QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id_); this_cache_dir.mkpath("."); diff --git a/app/render/backend/videorenderframecache.h b/app/render/backend/videorenderframecache.h index de89ec123..74a40f286 100644 --- a/app/render/backend/videorenderframecache.h +++ b/app/render/backend/videorenderframecache.h @@ -28,17 +28,19 @@ public: /** * @brief Return the path of the cached image at this time */ - QString CachePathName(const QByteArray &hash); + QString CachePathName(const QByteArray &hash) const; void SetCacheID(const QString& id); - QByteArray TimeToHash(const rational& time); + QByteArray TimeToHash(const rational& time) const; void SetHash(const rational& time, const QByteArray& hash); void RemoveHash(const rational& time, const QByteArray &hash); void Truncate(const rational& time); + QList TimesWithHash(const QByteArray& hash); + private: void RemoveHashFromCurrentlyCaching(const QByteArray& hash); diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index d5b6a399c..b5a33c2d1 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -4,8 +4,8 @@ #include "node/node.h" #include "render/pixelservice.h" -VideoRenderWorker::VideoRenderWorker(DecoderCache *decoder_cache, VideoRenderFrameCache *frame_cache, QObject *parent) : - RenderWorker(decoder_cache, parent), +VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, QObject *parent) : + RenderWorker(parent), frame_cache_(frame_cache) { @@ -36,7 +36,7 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path) emit CompletedFrame(path, hash, value); } else { // Another thread must be caching this already, nothing to be done - emit HashAlreadyBeingCached(); + emit HashAlreadyBeingCached(path, hash); } return value; @@ -85,7 +85,7 @@ void VideoRenderWorker::HashNodeRecursively(QCryptographicHash *hash, Node* n, c // We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer if (input->data_type() == NodeParam::kFootage) { StreamPtr stream = ResolveStreamFromInput(input); - DecoderPtr decoder = ResolveDecoderFromInput(input); + DecoderPtr decoder = ResolveDecoderFromInput(stream); if (decoder != nullptr) { // Add footage details to hash @@ -99,10 +99,19 @@ void VideoRenderWorker::HashNodeRecursively(QCryptographicHash *hash, Node* n, c // Footage stream hash->addData(QString::number(stream->index()).toUtf8()); - // Footage timestamp - hash->addData(QString::number(decoder->GetTimestampFromTime(time)).toUtf8()); + if (stream->type() == Stream::kImage || stream->type() == Stream::kVideo) { + ImageStreamPtr video_stream = std::static_pointer_cast(stream); - // FIXME: Add colorspace and alpha assoc + // Footage timestamp + hash->addData(QString::number(decoder->GetTimestampFromTime(time)).toUtf8()); + + // Current colorspace + // FIXME: Handle empty colorspace... + hash->addData(video_stream->colorspace().toUtf8()); + + // Alpha associated setting + hash->addData(QString::number(video_stream->premultiplied_alpha()).toUtf8()); + } } } } @@ -130,8 +139,6 @@ void VideoRenderWorker::CloseInternal() void VideoRenderWorker::Download(NodeDependency dep, QByteArray hash, QVariant texture, QString filename) { - working_++; - PixelFormatInfo format_info = PixelService::GetPixelFormatInfo(video_params().format()); // Set up OIIO::ImageSpec for compressing cached images on disk @@ -153,8 +160,6 @@ void VideoRenderWorker::Download(NodeDependency dep, QByteArray hash, QVariant t } else { qWarning() << "Failed to open output file:" << filename; } - - working_--; } NodeValueTable VideoRenderWorker::RenderBlock(TrackOutput *track, const TimeRange &range) @@ -171,3 +176,8 @@ NodeValueTable VideoRenderWorker::RenderBlock(TrackOutput *track, const TimeRang return table; } + +ColorProcessorCache *VideoRenderWorker::color_cache() +{ + return &color_cache_; +} diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h index 7ed95ec5b..20f5d9b0f 100644 --- a/app/render/backend/videorenderworker.h +++ b/app/render/backend/videorenderworker.h @@ -3,6 +3,7 @@ #include +#include "colorprocessorcache.h" #include "node/dependency.h" #include "render/videoparams.h" #include "renderworker.h" @@ -11,7 +12,7 @@ class VideoRenderWorker : public RenderWorker { Q_OBJECT public: - VideoRenderWorker(DecoderCache* decoder_cache, VideoRenderFrameCache* frame_cache, QObject* parent = nullptr); + VideoRenderWorker(VideoRenderFrameCache* frame_cache, QObject* parent = nullptr); void SetParameters(const VideoRenderingParams& video_params); @@ -23,7 +24,7 @@ signals: void CompletedDownload(NodeDependency path, QByteArray hash); - void HashAlreadyBeingCached(); + void HashAlreadyBeingCached(NodeDependency path, QByteArray hash); void HashAlreadyExists(NodeDependency path, QByteArray hash); @@ -44,6 +45,8 @@ protected: virtual NodeValueTable RenderBlock(TrackOutput *track, const TimeRange& range) override; + ColorProcessorCache* color_cache(); + private: void ProcessNode(); @@ -53,6 +56,8 @@ private: VideoRenderFrameCache* frame_cache_; + ColorProcessorCache color_cache_; + QByteArray download_buffer_; private slots: diff --git a/app/render/colorprocessor.h b/app/render/colorprocessor.h index e575e2574..001b449fb 100644 --- a/app/render/colorprocessor.h +++ b/app/render/colorprocessor.h @@ -4,8 +4,10 @@ #include namespace OCIO = OCIO_NAMESPACE::v1; +#include "common/constructors.h" #include "decoder/frame.h" + class ColorProcessor; using ColorProcessorPtr = std::shared_ptr; @@ -19,6 +21,8 @@ public: QString view, const QString& look); + DISABLE_COPY_MOVE(ColorProcessor) + static ColorProcessorPtr Create(const QString& source_space, const QString& dest_space); static ColorProcessorPtr Create(const QString& source_space, diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 1493861b9..147c96b30 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -239,7 +239,7 @@ void TimelineWidget::PointerTool::InitiateDrag(const TimelineCoordinate &mouse_p olive::timeline::MovementMode trim_mode = olive::timeline::kNone; // FIXME: Hardcoded number - const int kTrimHandle = 20; + const int kTrimHandle = 10; qreal mouse_x = parent()->TimeToScene(mouse_pos.GetFrame());