diff --git a/app/core.cpp b/app/core.cpp index 8a8f42e44..d66495ed5 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -218,6 +218,7 @@ void Core::CreateNewFolder() } // FIXME: Test code +#include "node/blend/alphaover/alphaover.h" #include "node/output/timeline/timeline.h" #include "node/output/track/track.h" #include "node/output/viewer/viewer.h" @@ -303,6 +304,11 @@ void Core::CreateNewSequence() // Connect track to timeline NodeParam::ConnectEdge(to->track_output(), tb->track_input()); + // FIXME: Test code + AlphaOverBlend* blend = new AlphaOverBlend(); + new_sequence->AddNode(blend); + // End test code + vo->AttachViewer(olive::panel_focus_manager->MostRecentlyFocused()); tb->AttachTimeline(olive::panel_focus_manager->MostRecentlyFocused()); olive::panel_focus_manager->MostRecentlyFocused()->SetGraph(new_sequence.get()); diff --git a/app/node/blend/alphaover/alphaover.cpp b/app/node/blend/alphaover/alphaover.cpp index 6013ceae7..f5fb4056d 100644 --- a/app/node/blend/alphaover/alphaover.cpp +++ b/app/node/blend/alphaover/alphaover.cpp @@ -1,10 +1,27 @@ #include "alphaover.h" +#include "render/rendertypes.h" + AlphaOverBlend::AlphaOverBlend() { } +QString AlphaOverBlend::Name() +{ + return tr("Alpha Over"); +} + +QString AlphaOverBlend::id() +{ + return "org.olivevideoeditor.Olive.alphaoverblend"; +} + +QString AlphaOverBlend::Description() +{ + return tr("A blending node that composites one texture over another using its alpha channel."); +} + void AlphaOverBlend::Process(const rational &time) { Q_UNUSED(time) @@ -13,5 +30,10 @@ void AlphaOverBlend::Process(const rational &time) // Note that alpha will always be premultiplied by this point + + //GLuint base_tex = base_input_->get_value(time).value(); + + // FIXME: Does nothing + texture_output()->set_value(blend_input()->get_value(time).value()); } diff --git a/app/node/blend/alphaover/alphaover.h b/app/node/blend/alphaover/alphaover.h index 913679e1a..490bb1d04 100644 --- a/app/node/blend/alphaover/alphaover.h +++ b/app/node/blend/alphaover/alphaover.h @@ -8,6 +8,10 @@ class AlphaOverBlend : public BlendNode public: AlphaOverBlend(); + virtual QString Name() override; + virtual QString id() override; + virtual QString Description() override; + protected: virtual void Process(const rational &time) override; }; diff --git a/app/node/blend/blend.cpp b/app/node/blend/blend.cpp index 8a33d1a5c..a48a58d35 100644 --- a/app/node/blend/blend.cpp +++ b/app/node/blend/blend.cpp @@ -9,6 +9,15 @@ BlendNode::BlendNode() blend_input_ = new NodeInput("blend_in"); blend_input_->add_data_input(NodeParam::kTexture); AddParameter(blend_input_); + + texture_output_ = new NodeOutput("tex_out"); + texture_output_->set_data_type(NodeParam::kTexture); + AddParameter(texture_output_); +} + +QString BlendNode::Category() +{ + return tr("Blend"); } NodeInput *BlendNode::base_input() @@ -20,3 +29,8 @@ NodeInput *BlendNode::blend_input() { return blend_input_; } + +NodeOutput *BlendNode::texture_output() +{ + return texture_output_; +} diff --git a/app/node/blend/blend.h b/app/node/blend/blend.h index 7f63c96c4..209ea1f5d 100644 --- a/app/node/blend/blend.h +++ b/app/node/blend/blend.h @@ -8,14 +8,20 @@ class BlendNode : public Node public: BlendNode(); + virtual QString Category() override; + NodeInput* base_input(); NodeInput* blend_input(); + NodeOutput* texture_output(); + private: NodeInput* base_input_; NodeInput* blend_input_; + + NodeOutput* texture_output_; }; #endif // BLEND_H diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 86039e952..7f625f6c8 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -111,6 +111,9 @@ QList TrackOutput::GetImmediateDependenciesAt(const rational &time) nodes.append(current_block_); } + // The next track is not a direct dependency (it would only become so through a merge node) + nodes.removeAll(next_track()); + return nodes; } diff --git a/app/node/processor/renderer/CMakeLists.txt b/app/node/processor/renderer/CMakeLists.txt index 062bcad77..64d6f7a16 100644 --- a/app/node/processor/renderer/CMakeLists.txt +++ b/app/node/processor/renderer/CMakeLists.txt @@ -16,6 +16,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + node/processor/renderer/renderpath.h + node/processor/renderer/renderpath.cpp node/processor/renderer/renderer.h node/processor/renderer/renderer.cpp node/processor/renderer/rendererprobe.h diff --git a/app/node/processor/renderer/renderer.cpp b/app/node/processor/renderer/renderer.cpp index 4390b820a..489a28a2f 100644 --- a/app/node/processor/renderer/renderer.cpp +++ b/app/node/processor/renderer/renderer.cpp @@ -27,12 +27,14 @@ #include #include "render/rendertypes.h" +#include "renderpath.h" #include "rendererprobe.h" RendererProcessor::RendererProcessor() : started_(false), width_(0), - height_(0) + height_(0), + caching_(false) { texture_input_ = new NodeInput("tex_in"); texture_input_->add_data_input(NodeInput::kTexture); @@ -145,13 +147,15 @@ void RendererProcessor::InvalidateCache(const rational &start_range, const ratio << "and" << end_range.toDouble(); + // FIXME: Snap start_range to timebase + for (rational r=start_range;r<=end_range;r+=timebase_) { if (!cache_queue_.contains(r)) { cache_queue_.append(r); } } - CacheCallback(); + CacheNext(); Node::InvalidateCache(start_range, end_range); } @@ -192,6 +196,10 @@ void RendererProcessor::Start() for (int i=0;i(width_, height_, format_, mode_); threads_[i]->StartThread(QThread::HighPriority); + + // Ensure this connection is "Queued" so that it always runs in this object's threaded rather than any of the + // other threads + connect(threads_[i].get(), SIGNAL(FinishedPath()), this, SLOT(ThreadCallback()), Qt::QueuedConnection); } started_ = true; @@ -230,21 +238,33 @@ void RendererProcessor::GenerateCacheIDInternal() cache_id_ = bytes.toHex(); } -void RendererProcessor::CacheCallback() +void RendererProcessor::CacheNext() { - if (cache_queue_.isEmpty() || !texture_input_->IsConnected()) { + if (cache_queue_.isEmpty() || !texture_input_->IsConnected() || caching_) { return; } - rational time_to_cache = cache_queue_.first(); + // Make sure cache has started + Start(); + + rational time_to_cache = cache_queue_.takeFirst(); Node* node_to_cache = texture_input_->edges().first()->output()->parent(); - RendererProbe::ProbeNode(node_to_cache, QThread::idealThreadCount(), time_to_cache); + // Run this probe in another thread + RenderPath path = RendererProbe::ProbeNode(node_to_cache, threads_.size(), time_to_cache); + + cache_return_count_ = 0; + + for (int i=0;iQueue(path.GetThreadPath(i), time_to_cache); + } + + caching_ = true; /* - // Make sure cache has started - Start(); + bool caching = false; @@ -267,6 +287,18 @@ void RendererProcessor::CacheCallback() */ } +void RendererProcessor::ThreadCallback() +{ + cache_return_count_++; + + if (cache_return_count_ == threads_.size()) { + // Threads are all done now, time to proceed + caching_ = false; + + CacheNext(); + } +} + RendererThread* RendererProcessor::CurrentThread() { return dynamic_cast(QThread::currentThread()); diff --git a/app/node/processor/renderer/renderer.h b/app/node/processor/renderer/renderer.h index f5a1cb8a1..629c6af11 100644 --- a/app/node/processor/renderer/renderer.h +++ b/app/node/processor/renderer/renderer.h @@ -117,7 +117,7 @@ private: * * This function is NOT thread-safe and should only be called in the main thread. */ - void CacheCallback(); + void CacheNext(); /** * @brief Internal list of RenderThreads @@ -148,6 +148,11 @@ private: QString cache_name_; qint64 cache_time_; QString cache_id_; + bool caching_; + int cache_return_count_; + +private slots: + void ThreadCallback(); }; diff --git a/app/node/processor/renderer/rendererprobe.cpp b/app/node/processor/renderer/rendererprobe.cpp index 32ada05e6..fda34767c 100644 --- a/app/node/processor/renderer/rendererprobe.cpp +++ b/app/node/processor/renderer/rendererprobe.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 "rendererprobe.h" #include @@ -7,29 +27,55 @@ RendererProbe::RendererProbe() } -void RendererProbe::ProbeNode(Node *node, int thread_count, const rational& time) +RenderPath RendererProbe::ProbeNode(Node *node, int thread_count, const rational& time) { Q_ASSERT(thread_count > 0); - //QVector< QVector > dependency_graph; + RenderPath render_path(thread_count); - //dependency_graph.resize(thread_count); + TraverseNode(render_path, node, time, 0, 0); - TraverseNode(node, 0, time); + render_path.Finalize(); + + return render_path; } -void RendererProbe::TraverseNode(Node *node, int thread, const rational& time) +void RendererProbe::TraverseNode(RenderPath& path, Node *node, const rational& time, int thread, int index) { - qDebug() << node << "will run on thread" << thread; + bool can_take_node = true; + + if (path.ContainsNode(node) >= 0) { + if (path.NodeIndex(node) < index) { + // If the other thread's index is smaller, it means we can do it earlier here so we should "steal" it + qDebug() << "Stealing" << node << "to" << thread; + path.RemoveEntry(node); + } else { + // Otherwise, we'll be delaying it and there's no point to that + can_take_node = false; + } + } + + // Add this node to the thread path + if (can_take_node) { + qDebug() << node << "will run on thread" << thread << "at index" << index; + path.AddEntry(node, thread, index); + } QList deps = node->GetImmediateDependenciesAt(time); + // Print debugging information foreach (Node* dep, deps) { qDebug() << " Dependency found:" << dep; } + int next_index = index + 1; + foreach (Node* dep, deps) { - TraverseNode(dep, thread, time); - thread++; + int ideal_thread = path.FindAvailableThreadAtIndex(index); + + // FIXME: This will throw this thread's index off by one, test whether this is okay + int run_thread = (ideal_thread == -1) ? thread : ideal_thread; + + TraverseNode(path, dep, time, run_thread, next_index); } } diff --git a/app/node/processor/renderer/rendererprobe.h b/app/node/processor/renderer/rendererprobe.h index e06b030ed..83d09cb5f 100644 --- a/app/node/processor/renderer/rendererprobe.h +++ b/app/node/processor/renderer/rendererprobe.h @@ -1,17 +1,39 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 RENDERERPROBE_H #define RENDERERPROBE_H #include "node/node.h" +#include "renderpath.h" class RendererProbe { public: RendererProbe(); - static void ProbeNode(Node* node, int thread_count, const rational &time); + static RenderPath ProbeNode(Node* node, int thread_count, const rational &time); private: - static void TraverseNode(Node* node, int thread, const rational& time); + static void TraverseNode(RenderPath& path, Node* node, const rational& time, int thread, int index); + }; #endif // RENDERERPROBE_H diff --git a/app/node/processor/renderer/rendererthread.cpp b/app/node/processor/renderer/rendererthread.cpp index d24f78fc9..67bc2caf4 100644 --- a/app/node/processor/renderer/rendererthread.cpp +++ b/app/node/processor/renderer/rendererthread.cpp @@ -32,33 +32,25 @@ RendererThread::RendererThread(const int &width, const int &height, const olive: { } -bool RendererThread::Queue(Node *n, const rational& time) +void RendererThread::Queue(const RenderThreadPath &path, const rational& time) { - // If the thread is inactive, tryLock() will succeed - if (mutex_.tryLock()) { + // Wait for thread to be available + mutex_.lock(); - qDebug() << "[RendererThread Main] tryLock succeeded"; + // We can now change params without the other thread using them + path_ = path; + time_ = time; - // The mutex is locked in the calling thread now, so we can change the active Node - node_ = n; - time_ = time; + // Prepare to wait for thread to respond + caller_mutex_.lock(); - // We can now wake up our main thread - qDebug() << "[RendererThread Main] Waking thread"; - wait_cond_.wakeAll(); - qDebug() << "[RendererThread Main] Unlocking mutex"; - mutex_.unlock(); + // Wake up our main thread + wait_cond_.wakeAll(); + mutex_.unlock(); - // Wait for thread to start before returning - qDebug() << "[RendererThread Main] Waiting for caller mutex"; - caller_mutex_.lock(); - qDebug() << "[RendererThread Main] Caller mutex arrived"; - caller_mutex_.unlock(); - - return true; - } - - return false; + // Wait for thread to start before returning + wait_cond_.wait(&caller_mutex_); + caller_mutex_.unlock(); } void RendererThread::Cancel() @@ -80,6 +72,11 @@ void RendererThread::run() // Lock mutex for main loop mutex_.lock(); + // Signal that main thread can continue now + caller_mutex_.lock(); + wait_cond_.wakeAll(); + caller_mutex_.unlock(); + RenderInstance instance(width_, height_, format_, mode_); render_instance_ = &instance; @@ -88,17 +85,20 @@ void RendererThread::run() // Main loop (use Cancel() to exit it) while (!cancelled_) { - // Lock the caller mutex (used in Queue() for thread synchronization) - caller_mutex_.lock(); - // Main waiting condition wait_cond_.wait(&mutex_); - // Unlock the caller mutex + // Wake up main thread + caller_mutex_.lock(); + wait_cond_.wakeAll(); caller_mutex_.unlock(); // Process the Node - node_->Run(time_); + for (int i=path_.size()-1;i>=0;i--) { + path_.at(i)->Run(time_); + } + + emit FinishedPath(); } } @@ -112,8 +112,15 @@ void RendererThread::run() void RendererThread::StartThread(QThread::Priority priority) { - queue_.clear(); + path_.clear(); - // Start the thread (the thread will unlock caller_mutex_) + caller_mutex_.lock(); + + // Start the thread QThread::start(priority); + + // Wait for thread to finish completion + wait_cond_.wait(&caller_mutex_); + + caller_mutex_.unlock(); } diff --git a/app/node/processor/renderer/rendererthread.h b/app/node/processor/renderer/rendererthread.h index 1f97f1c5f..91856cd80 100644 --- a/app/node/processor/renderer/rendererthread.h +++ b/app/node/processor/renderer/rendererthread.h @@ -28,21 +28,18 @@ #include "node/node.h" #include "render/renderinstance.h" - -struct RenderQueueEntry { - Node* node; - rational time; -}; +#include "renderpath.h" class RendererThread : public QThread { + Q_OBJECT public: RendererThread(const int& width, const int& height, const olive::PixelFormat& format, const olive::RenderMode& mode); - bool Queue(Node* n, const rational &time); + void Queue(const RenderThreadPath& path, const rational &time); void Cancel(); @@ -52,6 +49,9 @@ public: void StartThread(Priority priority = InheritPriority); +signals: + void FinishedPath(); + private: QWaitCondition wait_cond_; @@ -59,7 +59,7 @@ private: QMutex caller_mutex_; - Node* node_; + RenderThreadPath path_; rational time_; @@ -75,8 +75,6 @@ private: RenderInstance* render_instance_; - QVector queue_; - }; using RendererThreadPtr = std::shared_ptr; diff --git a/app/node/processor/renderer/renderpath.cpp b/app/node/processor/renderer/renderpath.cpp new file mode 100644 index 000000000..4f0e4739b --- /dev/null +++ b/app/node/processor/renderer/renderpath.cpp @@ -0,0 +1,88 @@ +#include "renderpath.h" + +RenderPath::RenderPath(int max_threads) : + finalized_(false) +{ + Q_ASSERT(max_threads > 0); + + render_path_.resize(max_threads); +} + +void RenderPath::AddEntry(Node *node, int thread, int index) +{ + if (finalized_) { + return; + } + + RenderThreadPath& thread_path = render_path_[thread]; + + // Make sure there are enough indices for this entry + while (thread_path.size() < index) { + thread_path.append(nullptr); + } + + thread_path.append(node); + thread_map_.insert(node, thread); +} + +void RenderPath::RemoveEntry(Node *node) +{ + if (finalized_) { + return; + } + + Q_ASSERT(thread_map_.contains(node)); + + int node_thread = thread_map_[node]; + + render_path_[node_thread].replace(NodeIndexInThread(node, node_thread), nullptr); + thread_map_.remove(node); +} + +int RenderPath::ContainsNode(Node *node) +{ + if (thread_map_.contains(node)) { + return thread_map_[node]; + } + return -1; +} + +int RenderPath::NodeIndex(Node *node) +{ + if (thread_map_.contains(node)) { + return NodeIndexInThread(node, thread_map_[node]); + } + return -1; +} + +int RenderPath::NodeIndexInThread(Node *node, int thread) +{ + return render_path_[thread].indexOf(node); +} + +void RenderPath::Finalize() +{ + for (int i=0;i& thread_path_ = render_path_[i]; + + if (index >= thread_path_.size() || thread_path_.at(index) == nullptr) { + return i; + } + } + + return -1; +} + +RenderThreadPath &RenderPath::GetThreadPath(int thread) +{ + return render_path_[thread]; +} diff --git a/app/node/processor/renderer/renderpath.h b/app/node/processor/renderer/renderpath.h new file mode 100644 index 000000000..03b924b14 --- /dev/null +++ b/app/node/processor/renderer/renderpath.h @@ -0,0 +1,82 @@ +#ifndef RENDERPATH_H +#define RENDERPATH_H + +#include + +#include "node/node.h" + +using RenderThreadPath = QVector; + +class RenderPath +{ +public: + RenderPath(int max_threads); + + /** + * @brief Add a Node to this thread at this index + * + * The Node is guaranteed to be added to at least this index. The index may be higher if this thread has other Nodes + * taking up this index, but it will never be lower. + */ + void AddEntry(Node* node, int thread, int index); + + /** + * @brief Removes a Node, replacing its entry with nullptr + * + * This function asserts whether the Node was added initially to help aid bug detection. + * + * @param node + */ + void RemoveEntry(Node* node); + + /** + * @brief Determine whether the RenderPath already contains this Node + * + * @return + * + * The thread index if one contains this Node, or -1 if none of them do + */ + int ContainsNode(Node* node); + + /** + * @brief Determine what index a Node has in whatever thread it's in + * + * @return + * + * The Node's index in its thread, or -1 if no threads have this Node. + */ + int NodeIndex(Node* node); + + /** + * @brief Determine what index a Node has in a particular thread + * + * @return + * + * The Node's index in this thread, or -1 if this thread does not have this Node. + */ + int NodeIndexInThread(Node* node, int thread); + + /** + * @brief Finalize the render path ready for executing + * + * Probing uses indices to determine what Nodes occur where. Depending on the state of the graph, many of these + * indices will be empty across all the threads which, while helpful for probing, is unnecessary for executing. + * Running this function assumes no further probing will occur. + * + * Finalizing places this RenderPath into more-or-less a read-only state. + */ + void Finalize(); + + int FindAvailableThreadAtIndex(int index); + + RenderThreadPath& GetThreadPath(int thread); + +private: + QVector render_path_; + + QMap thread_map_; + + bool finalized_; +}; + +#endif // RENDERPATH_H diff --git a/app/render/gl/shadergenerators.cpp b/app/render/gl/shadergenerators.cpp index c5d263a0a..4274eb6dc 100644 --- a/app/render/gl/shadergenerators.cpp +++ b/app/render/gl/shadergenerators.cpp @@ -84,8 +84,8 @@ ShaderPtr ShaderGenerator::DefaultPipeline(const QString& function_name, const Q // If additional code was passed, add it and reference it in main(). // - // The function in the additional code is expected to be `vec4 function_name(vec4 color)`. The texture coordinate can be - // acquired through `v_texcoord`. + // The function in the additional code is expected to be `vec4 function_name(vec4 color)`. The texture coordinate + // can be acquired through `v_texcoord`. frag_shader.append(shader_code);