diff --git a/app/core.cpp b/app/core.cpp index b96f5a08b..8a8f42e44 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -270,12 +270,25 @@ void Core::CreateNewSequence() new_sequence); TimelineOutput* tb = new TimelineOutput(); + tb->SetTimebase(new_sequence->video_time_base()); new_sequence->AddNode(tb); RendererProcessor* rp = new RendererProcessor(); + + // Set renderer's parameters based on sequence's parameters + rp->SetParameters(new_sequence->video_width(), + new_sequence->video_height(), + olive::PIX_FMT_RGBA16F, // FIXME: Make this configurable + olive::RenderMode::kOffline); + + // Set the "cache name" only here to aid the cache ID's uniqueness + rp->SetCacheName(new_sequence->name()); + rp->SetTimebase(new_sequence->video_time_base()); + new_sequence->AddNode(rp); ViewerOutput* vo = new ViewerOutput(); + vo->SetTimebase(new_sequence->video_time_base()); new_sequence->AddNode(vo); TrackOutput* to = new TrackOutput(); diff --git a/app/decoder/ffmpeg/ffmpegdecoder.cpp b/app/decoder/ffmpeg/ffmpegdecoder.cpp index aa0650f17..246f3a4bb 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.cpp +++ b/app/decoder/ffmpeg/ffmpegdecoder.cpp @@ -211,24 +211,30 @@ FramePtr FFmpegDecoder::Retrieve(const rational &timecode, const rational &lengt // Set up seeking loop int64_t seek_ts = target_ts; int64_t second_ts = qRound(rational(avstream_->time_base).flipped().toDouble()); + bool last_backtrack = false; // FFmpeg frame retrieve loop while (ret >= 0 && frame_->pts != target_ts) { // If the frame timestamp is too large, we need to seek back a little if (frame_->pts > target_ts || frame_->pts == AV_NOPTS_VALUE) { - avcodec_flush_buffers(codec_ctx_); - av_seek_frame(fmt_ctx_, avstream_->index, seek_ts, AVSEEK_FLAG_BACKWARD); - - // FFmpeg doesn't always seek correctly, if we have to seek again we wrangle it into seeking back far enough - // If we already tried seeking to 0 though, there's nothing we can do so we error here - if (seek_ts == 0) { + if (last_backtrack) { Error(tr("FFmpeg failed to seek to the correct location")); return nullptr; } - seek_ts = qMax(0L, seek_ts - second_ts); + // We can't seek earlier than 0, so if this is a 0-seek, don't try any more times after this attempt + if (seek_ts <= 0) { + seek_ts = 0; + last_backtrack = true; + } + + avcodec_flush_buffers(codec_ctx_); + av_seek_frame(fmt_ctx_, avstream_->index, seek_ts, AVSEEK_FLAG_BACKWARD); + + // FFmpeg doesn't always seek correctly, if we have to seek again we wrangle it into seeking back far enough + seek_ts -= second_ts; } ret = GetFrame(); diff --git a/app/node/blend/alphaover/alphaover.h b/app/node/blend/alphaover/alphaover.h index 39d3d0a4f..913679e1a 100644 --- a/app/node/blend/alphaover/alphaover.h +++ b/app/node/blend/alphaover/alphaover.h @@ -8,7 +8,7 @@ class AlphaOverBlend : public BlendNode public: AlphaOverBlend(); -public slots: +protected: virtual void Process(const rational &time) override; }; diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 7b17dff5b..80575ac70 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -22,16 +22,13 @@ #include -Block::Block() +Block::Block() : + next_(nullptr) { previous_input_ = new NodeInput("prev_block"); previous_input_->add_data_input(NodeParam::kBlock); AddParameter(previous_input_); - next_input_ = new NodeInput("next_block"); - next_input_->add_data_input(NodeParam::kBlock); - AddParameter(next_input_); - block_output_ = new NodeOutput("block_out"); block_output_->set_data_type(NodeParam::kBlock); AddParameter(block_output_); @@ -40,8 +37,8 @@ Block::Block() texture_output_->set_data_type(NodeParam::kTexture); AddParameter(texture_output_); - connect(this, SIGNAL(EdgeAdded(NodeEdgePtr)), this, SLOT(BlockOrderChanged(NodeEdgePtr))); - connect(this, SIGNAL(EdgeRemoved(NodeEdgePtr)), this, SLOT(BlockOrderChanged(NodeEdgePtr))); + connect(this, SIGNAL(EdgeAdded(NodeEdgePtr)), this, SLOT(EdgeAddedSlot(NodeEdgePtr))); + connect(this, SIGNAL(EdgeRemoved(NodeEdgePtr)), this, SLOT(EdgeRemovedSlot(NodeEdgePtr))); } QString Block::Category() @@ -78,7 +75,7 @@ Block *Block::previous() Block *Block::next() { - return ValueToPtr(next_input_->get_value(0)); + return next_; } NodeInput *Block::previous_input() @@ -86,11 +83,6 @@ NodeInput *Block::previous_input() return previous_input_; } -NodeInput *Block::next_input() -{ - return next_input_; -} - void Block::Process(const rational &time) { Q_UNUSED(time) @@ -99,6 +91,26 @@ void Block::Process(const rational &time) block_output_->set_value(PtrToValue(this)); } +void Block::EdgeAddedSlot(NodeEdgePtr edge) +{ + if (edge->input() == previous_input()) { + static_cast(edge->output()->parent())->next_ = this; + + // The blocks surrounding this one have changed, we need to Refresh() + RefreshFollowing(); + } +} + +void Block::EdgeRemovedSlot(NodeEdgePtr edge) +{ + if (edge->input() == previous_input()) { + static_cast(edge->output()->parent())->next_ = nullptr; + + // The blocks surrounding this one have changed, we need to Refresh() + RefreshFollowing(); + } +} + void Block::Refresh() { // Set in point to the out point of the previous Node @@ -111,6 +123,8 @@ void Block::Refresh() // Update out point by adding this clip's length to the just calculated in point out_point_ = in_point_ + length(); + InvalidateCache(in_point_, out_point_); + emit Refreshed(); } @@ -127,14 +141,6 @@ void Block::RefreshFollowing() } } -void Block::BlockOrderChanged(NodeEdgePtr edge) -{ - if (edge->input() == previous_input() || edge->input() == next_input()) { - // The blocks surrounding this one have changed, we need to Refresh() - RefreshFollowing(); - } -} - NodeOutput *Block::texture_output() { return texture_output_; @@ -148,13 +154,11 @@ NodeOutput *Block::block_output() void Block::ConnectBlocks(Block *previous, Block *next) { NodeParam::ConnectEdge(previous->block_output(), next->previous_input()); - NodeParam::ConnectEdge(next->block_output(), previous->next_input()); } void Block::DisconnectBlocks(Block *previous, Block *next) { NodeParam::DisconnectEdge(previous->block_output(), next->previous_input()); - NodeParam::DisconnectEdge(next->block_output(), previous->next_input()); } const rational &Block::media_in() @@ -168,6 +172,18 @@ void Block::set_media_in(const rational &media_in) media_in_ = media_in; // Signal that this clips contents have changed - //InvalidateCache(in(), out()); + InvalidateCache(in(), out()); } } + +QList Block::GetImmediateDependenciesAt(const rational &time) +{ + Q_UNUSED(time) + + QList nodes = Node::GetImmediateDependencies(); + + // Swap attached block for current block at this time + nodes.removeAll(previous()); + + return nodes; +} diff --git a/app/node/block/block.h b/app/node/block/block.h index 5deacd0d3..d932ed3b9 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -53,11 +53,10 @@ public: virtual const rational &length(); virtual void set_length(const rational &length); - virtual Block* previous(); - virtual Block* next(); + Block* previous(); + Block* next(); NodeInput* previous_input(); - NodeInput* next_input(); NodeOutput* texture_output(); NodeOutput* block_output(); @@ -68,9 +67,12 @@ public: const rational& media_in(); void set_media_in(const rational& media_in); -public slots: - virtual void Process(const rational &time) override; + /** + * @brief Override removes previous input as that is not a direct dependency + */ + virtual QList GetImmediateDependenciesAt(const rational &time) override; +public slots: /** * @brief Refreshes internal cache of in/out points up to date * @@ -100,10 +102,10 @@ signals: void Refreshed(); protected: + virtual void Process(const rational &time) override; private: NodeInput* previous_input_; - NodeInput* next_input_; NodeOutput* block_output_; NodeOutput* texture_output_; @@ -115,8 +117,12 @@ private: rational media_in_; + Block* next_; + private slots: - void BlockOrderChanged(NodeEdgePtr edge); + void EdgeAddedSlot(NodeEdgePtr edge); + + void EdgeRemovedSlot(NodeEdgePtr edge); }; diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index ed6cf5ed6..7fd034c65 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -42,7 +42,7 @@ public: NodeInput* texture_input(); -public slots: +protected: virtual void Process(const rational &time) override; private: diff --git a/app/node/generator/solid/solid.h b/app/node/generator/solid/solid.h index 18b79eca0..d4537461a 100644 --- a/app/node/generator/solid/solid.h +++ b/app/node/generator/solid/solid.h @@ -41,7 +41,7 @@ public: NodeOutput* texture_output(); -public slots: +protected: virtual void Process(const rational &time) override; private: diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index 004835413..7999fcf7c 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -27,17 +27,24 @@ // FIXME: Test code only #include "decoder/ffmpeg/ffmpegdecoder.h" -#include "render/colorservice.h" +#include "node/processor/renderer/renderer.h" #include "render/pixelservice.h" +#include "render/gl/shadergenerators.h" +#include "render/gl/functions.h" // End test code MediaInput::MediaInput() : + //ocio_shader_(nullptr), decoder_(nullptr) { footage_input_ = new NodeInput("footage_in"); footage_input_->add_data_input(NodeInput::kFootage); AddParameter(footage_input_); + matrix_input_ = new NodeInput("matrix_in"); + matrix_input_->add_data_input(NodeInput::kMatrix); + AddParameter(matrix_input_); + texture_output_ = new NodeOutput("tex_out"); texture_output_->set_data_type(NodeOutput::kTexture); AddParameter(texture_output_); @@ -65,6 +72,8 @@ QString MediaInput::Description() void MediaInput::Release() { + buffer_.Destroy(); + decoder_ = nullptr; } @@ -81,11 +90,17 @@ void MediaInput::SetFootage(Footage *f) void MediaInput::Process(const rational &time) { - // FIXME: Use OCIO for color management - // Set default texture to no texture texture_output_->set_value(0); + // Find the current Renderer instance + RenderInstance* renderer = RendererProcessor::CurrentInstance(); + + // If nothing is available, don't return a texture + if (renderer == nullptr) { + return; + } + // Get currently selected Footage Footage* footage = ValueToPtr(footage_input_->get_value(time)); @@ -114,23 +129,30 @@ void MediaInput::Process(const rational &time) return; } + /*renderer->buffer()->Upload(frame->data()); + + texture_output_->set_value(renderer->buffer()->texture());*/ + // Convert the frame to the Renderer format - //frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA16F); +// frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA16F); // Convert the frame to the Renderer color space - //ColorService::ConvertFrame(frame); + //color_service_.ConvertFrame(frame); - // FIXME: Test code - if (tex_buf_.IsCreated()) { - tex_buf_.Upload(frame->data()); + // Upload this frame to the GPU + /*if (buffer_.IsCreated()) { + buffer_.Upload(frame->data()); } else { - tex_buf_.Create(QOpenGLContext::currentContext(), + buffer_.Create(QOpenGLContext::currentContext(), static_cast(frame->format()), frame->width(), frame->height(), frame->data()); - } + }*/ - texture_output_->set_value(tex_buf_.texture()); + // Draw according to matrix + // BLIT + + //texture_output_->set_value(tex_buf_.texture()); // End test code } diff --git a/app/node/input/media/media.h b/app/node/input/media/media.h index 40b3595ee..733b3d26f 100644 --- a/app/node/input/media/media.h +++ b/app/node/input/media/media.h @@ -25,9 +25,11 @@ #include "decoder/decoder.h" #include "node/node.h" +#include "render/colorservice.h" // FIXME: Test code only #include "render/texturebuffer.h" +#include "render/gl/shaderptr.h" // End test code /** @@ -53,20 +55,22 @@ public: void SetFootage(Footage* f); -public slots: +protected: virtual void Process(const rational &time) override; private: NodeInput* footage_input_; + NodeInput* matrix_input_; + NodeOutput* texture_output_; - // FIXME: TEST CODE ONLY - TextureBuffer tex_buf_; - // END TEST CODE + TextureBuffer buffer_; DecoderPtr decoder_; + ColorService color_service_; + }; #endif // IMAGE_H diff --git a/app/node/node.cpp b/app/node/node.cpp index 80ab84542..76f7669fe 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -20,9 +20,12 @@ #include "node.h" +#include + #include "common/qobjectlistcast.h" -Node::Node() +Node::Node() : + last_time_(-1) { } @@ -63,18 +66,45 @@ void Node::InvalidateCache(const rational &start_range, const rational &end_rang QList params = parameters(); // Loop through all parameters (there should be no children that are not NodeParams) - for (int i=0;itype() == NodeParam::kOutput) { - for (int i=0;iedges().size();i++) { - param->edges().at(i)->input()->parent()->InvalidateCache(start_range, end_range); + + foreach (NodeEdgePtr edge, param->edges()) { + + NodeInput* connected_input = edge->input(); + Node* connected_node = connected_input->parent(); + + // Only send this signal if the Node isn't ignoring invalidate cache signals from this input + if (!connected_node->ignore_invalid_cache_inputs_.contains(connected_input)) { + connected_node->InvalidateCache(start_range, end_range); + } } } } } +void Node::IgnoreCacheInvalidationFrom(NodeInput *input) +{ + ignore_invalid_cache_inputs_.append(input); +} + +void Node::Run(const rational &time) +{ + lock_.lock(); + + if (last_time_ != time) { + // The results will be the same, so return here + Process(time); + + last_time_ = time; + } + + + lock_.unlock(); +} + NodeParam *Node::ParamAt(int index) { return static_cast(children().at(index)); @@ -97,8 +127,13 @@ int Node::IndexOfParameter(NodeParam *param) /** * @brief Recursively collects dependencies of Node `n` and appends them to QList `list` + * + * @param traverse + * + * TRUE to recursively traverse each node for a complete dependency graph. FALSE to return only the immediate + * dependencies. */ -void GetDependenciesInternal(Node* n, QList& list) { +void GetDependenciesInternal(Node* n, QList& list, bool traverse) { QList params = n->parameters(); foreach (NodeParam* p, params) { @@ -109,7 +144,10 @@ void GetDependenciesInternal(Node* n, QList& list) { Node* connected_node = edge->output()->parent(); list.append(connected_node); - GetDependenciesInternal(connected_node, list); + + if (traverse) { + GetDependenciesInternal(connected_node, list, traverse); + } } } } @@ -119,7 +157,7 @@ QList Node::GetDependencies() { QList node_list; - GetDependenciesInternal(this, node_list); + GetDependenciesInternal(this, node_list, true); return node_list; } @@ -159,6 +197,22 @@ QList Node::GetExclusiveDependencies() return deps; } +QList Node::GetImmediateDependencies() +{ + QList node_list; + + GetDependenciesInternal(this, node_list, false); + + return node_list; +} + +QList Node::GetImmediateDependenciesAt(const rational &time) +{ + Q_UNUSED(time) + + return GetImmediateDependencies(); +} + bool Node::OutputsTo(Node *n) { QList params = parameters(); diff --git a/app/node/node.h b/app/node/node.h index 53b02cb0a..4b30d4f12 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -21,6 +21,7 @@ #ifndef NODE_H #define NODE_H +#include #include #include "common/rational.h" @@ -121,6 +122,18 @@ public: */ QList GetExclusiveDependencies(); + /** + * @brief Retrieve immediate dependencies (only nodes that are directly connected to the inputs of this one) + */ + QList GetImmediateDependencies(); + + /** + * @brief For nodes that have different dependencies at different times, this function can be used for that purpose + * + * Only retrieves immmediate dependencies, meaning only nodes that are directly + */ + virtual QList GetImmediateDependenciesAt(const rational& time); + /** * @brief Returns whether this Node outputs data to the Node `n` in any way */ @@ -165,7 +178,11 @@ protected: */ virtual void InvalidateCache(const rational& start_range, const rational& end_range); -public slots: + /** + * @brief If we receive a signal from NodeInput `input`, don't propagate it. + */ + void IgnoreCacheInvalidationFrom(NodeInput* input); + /** * @brief The main processing function * @@ -181,6 +198,11 @@ public slots: */ virtual void Process(const rational& time) = 0; +public slots: + + + void Run(const rational& time); + signals: /** * @brief Signal emitted when a node is connected to another node (creating an "edge") @@ -205,6 +227,15 @@ private: * @brief Return whether a parameter with ID `id` has already been added to this Node */ bool HasParamWithID(const QString& id); + + /** + * @brief Internal list of inputs to ignore InvalidateCache() signals from + */ + QList ignore_invalid_cache_inputs_; + + rational last_time_; + + QMutex lock_; }; template diff --git a/app/node/output.cpp b/app/node/output.cpp index 05f971321..a8f74d947 100644 --- a/app/node/output.cpp +++ b/app/node/output.cpp @@ -50,7 +50,7 @@ void NodeOutput::set_data_type(const NodeParam::DataType &type) const QVariant &NodeOutput::get_value(const rational& time) { // Node::Process() should put the correct value in this output - parent()->Process(time); + parent()->Run(time); // The value should be have been set by this point return value_; diff --git a/app/node/output/timeline/timeline.cpp b/app/node/output/timeline/timeline.cpp index b7c0e1639..b59021d0d 100644 --- a/app/node/output/timeline/timeline.cpp +++ b/app/node/output/timeline/timeline.cpp @@ -162,6 +162,15 @@ void TimelineOutput::DetachTrack(TrackOutput *track) } } +void TimelineOutput::SetTimebase(const rational &timebase) +{ + timebase_ = timebase; + + if (attached_timeline_ != nullptr) { + attached_timeline_->SetTimebase(timebase_); + } +} + void TimelineOutput::AddTrack() { TrackOutput* track = new TrackOutput(); @@ -182,11 +191,9 @@ void TimelineOutput::TrackConnectionAdded(NodeEdgePtr edge) AttachTrack(attached_track()); - // FIXME: TEST CODE ONLY if (attached_timeline_ != nullptr) { - attached_timeline_->SetTimebase(rational(1001, 30000)); + attached_timeline_->SetTimebase(timebase_); } - // END TEST CODE } void TimelineOutput::TrackConnectionRemoved(NodeEdgePtr edge) diff --git a/app/node/output/timeline/timeline.h b/app/node/output/timeline/timeline.h index e47f80467..1a14c59f0 100644 --- a/app/node/output/timeline/timeline.h +++ b/app/node/output/timeline/timeline.h @@ -41,9 +41,11 @@ public: void AttachTimeline(TimelinePanel* timeline); + void SetTimebase(const rational& timebase); + NodeInput* track_input(); -public slots: +protected: virtual void Process(const rational &time) override; private: @@ -68,6 +70,8 @@ private: */ QVector track_cache_; + rational timebase_; + private slots: /** * @brief Slot for when the track connection is added diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 471467632..86039e952 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -99,6 +99,21 @@ void TrackOutput::Refresh() Block::Refresh(); } +QList TrackOutput::GetImmediateDependenciesAt(const rational &time) +{ + QList nodes = Node::GetImmediateDependencies(); + + ValidateCurrentBlock(time); + + // Swap attached block for current block at this time + nodes.removeAll(attached_block()); + if (current_block_ != this) { + nodes.append(current_block_); + } + + return nodes; +} + void TrackOutput::GenerateBlockWidgets() { foreach (Block* block, block_cache_) { @@ -136,29 +151,15 @@ void TrackOutput::Process(const rational &time) // Set track output correctly track_output_->set_value(PtrToValue(this)); - // This node representso the end of the timeline, so being beyond its in point is considered the end of the sequence - if (time >= in()) { + ValidateCurrentBlock(time); + + if (current_block_ == this) { + // No texture is valid texture_output()->set_value(0); - current_block_ = this; - return; + } else { + // At this point, we must have found the correct block so we use its texture output to produce the image + texture_output()->set_value(current_block_->texture_output()->get_value(time)); } - - // If we're here, we need to find the current clip to display - // attached_block() is guaranteed to not be nullptr if we didn't return before - current_block_ = attached_block(); - - // If the time requested is an earlier Block, traverse earlier until we find it - while (time < current_block_->in()) { - current_block_ = current_block_->previous(); - } - - // If the time requested is in a later Block, traverse later - while (time >= current_block_->out()) { - current_block_ = current_block_->next(); - } - - // At this point, we must have found the correct block so we use its texture output to produce the image - texture_output()->set_value(current_block_->texture_output()->get_value(time)); } void TrackOutput::InsertBlockBetweenBlocks(Block *block, Block *before, Block *after) @@ -243,6 +244,27 @@ void TrackOutput::AddBlockToGraph(Block *block) graph->AddNodeWithDependencies(block); } +void TrackOutput::ValidateCurrentBlock(const rational &time) +{ + // This node representso the end of the timeline, so being beyond its in point is considered the end of the sequence + if (time >= in()) { + current_block_ = this; + return; + } + + // If we're here, we need to find the current clip to display + + // If the time requested is an earlier Block, traverse earlier until we find it + while (time < current_block_->in()) { + current_block_ = current_block_->previous(); + } + + // If the time requested is in a later Block, traverse later + while (time >= current_block_->out()) { + current_block_ = current_block_->next(); + } +} + void TrackOutput::PlaceBlock(Block *block, rational start) { if (block_cache_.contains(block) && block->in() == start) { @@ -263,7 +285,6 @@ void TrackOutput::PlaceBlock(Block *block, rational start) } AppendBlock(block); - return; } diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index e884d0bd2..c39800d67 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -46,6 +46,11 @@ public: virtual void Refresh() override; + /** + * @brief Override swaps "attached block" with "current block" + */ + virtual QList GetImmediateDependenciesAt(const rational& time) override; + void GenerateBlockWidgets(); void DestroyBlockWidgets(); @@ -150,7 +155,7 @@ signals: */ void BlockRemoved(Block* block); -public slots: +protected: virtual void Process(const rational &time) override; private: @@ -171,6 +176,11 @@ private: */ void AddBlockToGraph(Block* block); + /** + * @brief Sets current_block_ to the correct attached Block based on `time` + */ + void ValidateCurrentBlock(const rational& time); + Block* attached_block(); QVector block_cache_; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index ef75ed92a..3c1d5e576 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -50,6 +50,15 @@ QString ViewerOutput::Description() return tr("Interface between a Viewer panel and the node system."); } +void ViewerOutput::SetTimebase(const rational &timebase) +{ + timebase_ = timebase; + + if (attached_viewer_ != nullptr) { + attached_viewer_->SetTimebase(timebase_); + } +} + NodeInput *ViewerOutput::texture_input() { return texture_input_; @@ -70,13 +79,14 @@ void ViewerOutput::AttachViewer(ViewerPanel *viewer) { // Disconnect old viewer if there's one attached if (attached_viewer_ != nullptr) { - disconnect(attached_viewer_, SIGNAL(TimeChanged(const rational&)), this, SLOT(Process(const rational&))); + disconnect(attached_viewer_, SIGNAL(TimeChanged(const rational&)), this, SLOT(Run(const rational&))); } // FIXME: Currently this attaches to ViewerPanels, but should it attached to Viewers instead? attached_viewer_ = viewer; if (attached_viewer_ != nullptr) { - connect(attached_viewer_, SIGNAL(TimeChanged(const rational&)), this, SLOT(Process(const rational&))); + connect(attached_viewer_, SIGNAL(TimeChanged(const rational&)), this, SLOT(Run(const rational&))); + SetTimebase(timebase_); } } diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 923f64f82..f0a1ed965 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -40,17 +40,21 @@ public: virtual QString Category() override; virtual QString Description() override; + void SetTimebase(const rational& timebase); + NodeInput* texture_input(); void AttachViewer(ViewerPanel* viewer); -public slots: +protected: virtual void Process(const rational &time) override; private: NodeInput* texture_input_; ViewerPanel* attached_viewer_; + + rational timebase_; }; #endif // VIEWER_H diff --git a/app/node/processor/renderer/CMakeLists.txt b/app/node/processor/renderer/CMakeLists.txt index 6f78b5cc4..062bcad77 100644 --- a/app/node/processor/renderer/CMakeLists.txt +++ b/app/node/processor/renderer/CMakeLists.txt @@ -18,6 +18,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} node/processor/renderer/renderer.h node/processor/renderer/renderer.cpp + node/processor/renderer/rendererprobe.h + node/processor/renderer/rendererprobe.cpp node/processor/renderer/rendererthread.h node/processor/renderer/rendererthread.cpp PARENT_SCOPE diff --git a/app/node/processor/renderer/renderer.cpp b/app/node/processor/renderer/renderer.cpp index 2fa7c391e..4390b820a 100644 --- a/app/node/processor/renderer/renderer.cpp +++ b/app/node/processor/renderer/renderer.cpp @@ -20,10 +20,19 @@ #include "renderer.h" +#include +#include #include +#include +#include + +#include "render/rendertypes.h" +#include "rendererprobe.h" RendererProcessor::RendererProcessor() : - started_(false) + started_(false), + width_(0), + height_(0) { texture_input_ = new NodeInput("tex_in"); texture_input_->add_data_input(NodeInput::kTexture); @@ -54,27 +63,73 @@ QString RendererProcessor::id() return "org.olivevideoeditor.Olive.renderervenus"; } +void RendererProcessor::SetCacheName(const QString &s) +{ + cache_name_ = s; + cache_time_ = QDateTime::currentMSecsSinceEpoch(); + + GenerateCacheIDInternal(); +} + void RendererProcessor::Process(const rational &time) { Q_UNUSED(time) + texture_output_->set_value(0); + + if (!texture_input_->IsConnected()) { + // Nothing is connected - nothing to show or render + return; + } + + if (cache_id_.isEmpty()) { + qWarning() << "RendererProcessor has no cache ID"; + return; + } + + if (timebase_.isNull()) { + qWarning() << "RendererProcessor has no timebase"; + return; + } + + // This Renderer node relies on a disk cache so this Process() function should be quite fast. Either it returns the + // cached frame or it returns nothing. + + // Perhaps it should lookahead to load textures into VRAM in advance? + + // Should it cache the final result or the result in an 8-bit image or a 16-bit intermediate image? + + /*qDebug() << QString("Requesting %1/%2").arg(QString::number(time.numerator()), QString::number(time.denominator())); + + if (cache_map_.contains(time)) { + qDebug() << " We have this frame!"; + } else { + qDebug() << " No frame at this address"; + cache_map_.insert(time, true); + }*/ + + + + + // FIXME: Test code only - GLuint tex = texture_input_->get_value(time).value(); + //GLuint tex = texture_input_->get_value(time).value(); //glReadPixels() - texture_output_->set_value(tex); + //texture_output_->set_value(tex); // End test code /* // Ensure we have started - Start(); - if (!started_) { - qWarning() << tr("An error occurred starting the Renderer node"); - return; - } + Start(); + if (!started_) { + qWarning() << tr("An error occurred starting the Renderer node"); + return; + } + } */ } @@ -85,8 +140,45 @@ void RendererProcessor::Release() void RendererProcessor::InvalidateCache(const rational &start_range, const rational &end_range) { - Q_UNUSED(start_range) - Q_UNUSED(end_range) + qDebug() << "[RendererProcessor] Cache invalidated between" + << start_range.toDouble() + << "and" + << end_range.toDouble(); + + for (rational r=start_range;r<=end_range;r+=timebase_) { + if (!cache_queue_.contains(r)) { + cache_queue_.append(r); + } + } + + CacheCallback(); + + Node::InvalidateCache(start_range, end_range); +} + +void RendererProcessor::SetTimebase(const rational &timebase) +{ + timebase_ = timebase; + timebase_dbl_ = timebase_.toDouble(); +} + +void RendererProcessor::SetParameters(const int &width, + const int &height, + const olive::PixelFormat &format, + const olive::RenderMode &mode) +{ + // Since we're changing parameters, all the existing threads are invalid and must be removed. They will start again + // next time this Node has to process anything. + Stop(); + + // Set new parameters + width_ = width; + height_ = height; + format_ = format; + mode_ = mode; + + // Regenerate the cache ID + GenerateCacheIDInternal(); } void RendererProcessor::Start() @@ -98,8 +190,8 @@ void RendererProcessor::Start() threads_.resize(QThread::idealThreadCount()); for (int i=0;i(); - threads_[i]->run(); + threads_[i] = std::make_shared(width_, height_, format_, mode_); + threads_[i]->StartThread(QThread::HighPriority); } started_ = true; @@ -120,11 +212,77 @@ void RendererProcessor::Stop() threads_.clear(); } +void RendererProcessor::GenerateCacheIDInternal() +{ + if (cache_name_.isEmpty() || width_ == 0 || height_ == 0) { + return; + } + + // Generate an ID that is more or less guaranteed to be unique to this Sequence + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(cache_name_.toUtf8()); + hash.addData(QString::number(cache_time_).toUtf8()); + hash.addData(QString::number(width_).toUtf8()); + hash.addData(QString::number(height_).toUtf8()); + hash.addData(QString::number(format_).toUtf8()); + + QByteArray bytes = hash.result(); + cache_id_ = bytes.toHex(); +} + +void RendererProcessor::CacheCallback() +{ + if (cache_queue_.isEmpty() || !texture_input_->IsConnected()) { + return; + } + + rational time_to_cache = cache_queue_.first(); + + Node* node_to_cache = texture_input_->edges().first()->output()->parent(); + + RendererProbe::ProbeNode(node_to_cache, QThread::idealThreadCount(), time_to_cache); + + /* + // Make sure cache has started + Start(); + + bool caching = false; + + // Look for a thread that's available + for (int i=0;iQueue(node_to_cache, time_to_cache)) { + // This thread is free and we've just taken control of it + caching = true; + break; + } + } + + if (caching) { + cache_queue_.removeFirst(); + + qDebug() << "[RendererProcessor] Ready to cache" << time_to_cache.numerator() << "/" << time_to_cache.denominator(); + } + */ +} + RendererThread* RendererProcessor::CurrentThread() { return dynamic_cast(QThread::currentThread()); } +RenderInstance *RendererProcessor::CurrentInstance() +{ + RendererThread* thread = CurrentThread(); + + if (thread != nullptr) { + return thread->render_instance(); + } + + return nullptr; +} + NodeInput *RendererProcessor::texture_input() { return texture_input_; diff --git a/app/node/processor/renderer/renderer.h b/app/node/processor/renderer/renderer.h index 4df3016bb..f5a1cb8a1 100644 --- a/app/node/processor/renderer/renderer.h +++ b/app/node/processor/renderer/renderer.h @@ -21,7 +21,11 @@ #ifndef RENDERER_H #define RENDERER_H +#include + #include "node/node.h" +#include "render/pixelformat.h" +#include "render/rendermodes.h" #include "rendererthread.h" /** @@ -44,10 +48,14 @@ public: virtual QString Description() override; virtual QString id() override; + void SetCacheName(const QString& s); + virtual void Release() override; virtual void InvalidateCache(const rational &start_range, const rational &end_range) override; + void SetTimebase(const rational& timebase); + /** * @brief Set parameters of the Renderer * @@ -66,7 +74,10 @@ public: * * Buffer pixel format */ - void SetParameters(const int& width, const int& height, const olive::PixelFormat& format); + void SetParameters(const int& width, + const int& height, + const olive::PixelFormat& format, + const olive::RenderMode& mode); /** * @brief Return current instance of a RenderThread (or nullptr if there is none) @@ -74,13 +85,15 @@ public: * This function attempts a dynamic_cast on QThread::currentThread() to RendererThread, which will return nullptr if * the cast fails (e.g. if this function is called from the main thread rather than a RendererThread). */ - static RendererThread *CurrentThread(); + static RendererThread* CurrentThread(); + + static RenderInstance* CurrentInstance(); NodeInput* texture_input(); NodeOutput* texture_output(); -public slots: +protected: virtual void Process(const rational &time) override; private: @@ -94,13 +107,48 @@ private: */ void Stop(); + /** + * @brief Internal function for generating the cache ID + */ + void GenerateCacheIDInternal(); + + /** + * @brief Function called when there are frames in the queue to cache + * + * This function is NOT thread-safe and should only be called in the main thread. + */ + void CacheCallback(); + + /** + * @brief Internal list of RenderThreads + */ QVector threads_; + /** + * @brief Internal variable that contains whether the Renderer has started or not + */ bool started_; NodeInput* texture_input_; NodeOutput* texture_output_; + + int width_; + + int height_; + + olive::PixelFormat format_; + + olive::RenderMode mode_; + + rational timebase_; + double timebase_dbl_; + + QVector cache_queue_; + QString cache_name_; + qint64 cache_time_; + QString cache_id_; + }; #endif // RENDERER_H diff --git a/app/node/processor/renderer/rendererprobe.cpp b/app/node/processor/renderer/rendererprobe.cpp new file mode 100644 index 000000000..32ada05e6 --- /dev/null +++ b/app/node/processor/renderer/rendererprobe.cpp @@ -0,0 +1,35 @@ +#include "rendererprobe.h" + +#include + +RendererProbe::RendererProbe() +{ + +} + +void RendererProbe::ProbeNode(Node *node, int thread_count, const rational& time) +{ + Q_ASSERT(thread_count > 0); + + //QVector< QVector > dependency_graph; + + //dependency_graph.resize(thread_count); + + TraverseNode(node, 0, time); +} + +void RendererProbe::TraverseNode(Node *node, int thread, const rational& time) +{ + qDebug() << node << "will run on thread" << thread; + + QList deps = node->GetImmediateDependenciesAt(time); + + foreach (Node* dep, deps) { + qDebug() << " Dependency found:" << dep; + } + + foreach (Node* dep, deps) { + TraverseNode(dep, thread, time); + thread++; + } +} diff --git a/app/node/processor/renderer/rendererprobe.h b/app/node/processor/renderer/rendererprobe.h new file mode 100644 index 000000000..e06b030ed --- /dev/null +++ b/app/node/processor/renderer/rendererprobe.h @@ -0,0 +1,17 @@ +#ifndef RENDERERPROBE_H +#define RENDERERPROBE_H + +#include "node/node.h" + +class RendererProbe +{ +public: + RendererProbe(); + + static void ProbeNode(Node* node, int thread_count, const rational &time); + +private: + static void TraverseNode(Node* node, int thread, const rational& time); +}; + +#endif // RENDERERPROBE_H diff --git a/app/node/processor/renderer/rendererthread.cpp b/app/node/processor/renderer/rendererthread.cpp index b029aaab1..d24f78fc9 100644 --- a/app/node/processor/renderer/rendererthread.cpp +++ b/app/node/processor/renderer/rendererthread.cpp @@ -22,8 +22,13 @@ #include -RendererThread::RendererThread() : - cancelled_(false) +RendererThread::RendererThread(const int &width, const int &height, const olive::PixelFormat &format, const olive::RenderMode &mode) : + cancelled_(false), + width_(width), + height_(height), + format_(format), + mode_(mode), + render_instance_(nullptr) { } @@ -32,16 +37,22 @@ bool RendererThread::Queue(Node *n, const rational& time) // If the thread is inactive, tryLock() will succeed if (mutex_.tryLock()) { + qDebug() << "[RendererThread Main] tryLock succeeded"; + // The mutex is locked in the calling thread now, so we can change the active Node node_ = n; time_ = time; // We can now wake up our main thread + qDebug() << "[RendererThread Main] Waking thread"; wait_cond_.wakeAll(); + qDebug() << "[RendererThread Main] Unlocking mutex"; 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; @@ -59,48 +70,50 @@ void RendererThread::Cancel() wait(); } +RenderInstance *RendererThread::render_instance() +{ + return render_instance_; +} + void RendererThread::run() { - // Create OpenGL context (automatically destroys any existing if there is one) - if (!ctx_.create()) { - qWarning() << tr("Failed to create OpenGL context in thread %1").arg(reinterpret_cast(this)); - return; - } - - // Create offscreen surface - surface_.create(); - - // Make context current on that surface - if (!ctx_.makeCurrent(&surface_)) { - qWarning() << tr("Failed to makeCurrent() on offscreen surface in thread %1").arg(reinterpret_cast(this)); - surface_.destroy(); - return; - } - // Lock mutex for main loop mutex_.lock(); - // Main loop (use Cancel() to exit it) - while (!cancelled_) { - // Lock the caller mutex (used in Queue() for thread synchronization) - caller_mutex_.lock(); + RenderInstance instance(width_, height_, format_, mode_); + render_instance_ = &instance; - // Main waiting condition - wait_cond_.wait(&mutex_); + // Allocate and create resources + if (instance.Start()) { - // Unlock the caller mutex - caller_mutex_.unlock(); + // Main loop (use Cancel() to exit it) + while (!cancelled_) { + // Lock the caller mutex (used in Queue() for thread synchronization) + caller_mutex_.lock(); - // Process the Node - node_->Process(time_); + // Main waiting condition + wait_cond_.wait(&mutex_); + + // Unlock the caller mutex + caller_mutex_.unlock(); + + // Process the Node + node_->Run(time_); + } } - // Release OpenGL context - ctx_.doneCurrent(); - - // Destroy offscreen surface - surface_.destroy(); + // Free all resources + render_instance_ = nullptr; + instance.Stop(); // Unlock mutex before exiting mutex_.unlock(); } + +void RendererThread::StartThread(QThread::Priority priority) +{ + queue_.clear(); + + // Start the thread (the thread will unlock caller_mutex_) + QThread::start(priority); +} diff --git a/app/node/processor/renderer/rendererthread.h b/app/node/processor/renderer/rendererthread.h index 08859214f..1f97f1c5f 100644 --- a/app/node/processor/renderer/rendererthread.h +++ b/app/node/processor/renderer/rendererthread.h @@ -23,36 +23,36 @@ #include #include -#include -#include #include #include #include "node/node.h" -#include "render/texturebuffer.h" +#include "render/renderinstance.h" + +struct RenderQueueEntry { + Node* node; + rational time; +}; class RendererThread : public QThread { public: - RendererThread(); - - QOpenGLContext* context(); - - TextureBuffer* buffer(); + RendererThread(const int& width, + const int& height, + const olive::PixelFormat& format, + const olive::RenderMode& mode); bool Queue(Node* n, const rational &time); void Cancel(); + RenderInstance* render_instance(); + virtual void run() override; + void StartThread(Priority priority = InheritPriority); + private: - QOpenGLContext ctx_; - - QOffscreenSurface surface_; - - TextureBuffer buffer_; - QWaitCondition wait_cond_; QMutex mutex_; @@ -64,6 +64,19 @@ private: rational time_; bool cancelled_; + + const int& width_; + + const int& height_; + + const olive::PixelFormat& format_; + + const olive::RenderMode& mode_; + + RenderInstance* render_instance_; + + QVector queue_; + }; using RendererThreadPtr = std::shared_ptr; diff --git a/app/panel/viewer/viewer.cpp b/app/panel/viewer/viewer.cpp index 88e57d0ce..197896cbf 100644 --- a/app/panel/viewer/viewer.cpp +++ b/app/panel/viewer/viewer.cpp @@ -44,6 +44,11 @@ void ViewerPanel::ZoomOut() viewer_->SetScale(viewer_->scale() * 0.5); } +void ViewerPanel::SetTimebase(const rational &timebase) +{ + viewer_->SetTimebase(timebase); +} + void ViewerPanel::SetTexture(GLuint tex) { viewer_->SetTexture(tex); diff --git a/app/panel/viewer/viewer.h b/app/panel/viewer/viewer.h index 29bc0f9ef..784268cf0 100644 --- a/app/panel/viewer/viewer.h +++ b/app/panel/viewer/viewer.h @@ -38,6 +38,8 @@ public: virtual void ZoomOut() override; + void SetTimebase(const rational& timebase); + public slots: /** * @brief Set the texture to draw and draw it diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 30bb964c7..16a537aec 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -20,14 +20,14 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} render/colorservice.h render/colorservice.cpp - #render/imagecache.h - #render/imagecache.cpp - #render/memorybuffer.h - #render/memorybuffer.cpp render/pixelformat.h render/pixelformat.cpp render/pixelservice.h render/pixelservice.cpp + render/renderinstance.h + render/renderinstance.cpp + render/rendermodes.h + render/rendertypes.h render/sampleformat.h render/texturebuffer.h render/texturebuffer.cpp diff --git a/app/render/colorservice.cpp b/app/render/colorservice.cpp index d702ed75a..b24d924ae 100644 --- a/app/render/colorservice.cpp +++ b/app/render/colorservice.cpp @@ -4,18 +4,15 @@ const int kRGBAChannels = 4; ColorService::ColorService() { + // FIXME: Hardcoded values for testing purposes + OCIO::ConstConfigRcPtr config = OCIO::Config::CreateFromFile("/run/media/matt/Home/OpenColorIO/ocio.configs.0.7v4/nuke-default/config.ocio"); + processor = config->getProcessor("srgb", + OCIO::ROLE_SCENE_LINEAR); } void ColorService::ConvertFrame(FramePtr f) { - OCIO::ConstConfigRcPtr config = OCIO::Config::CreateFromFile("/run/media/matt/Home/OpenColorIO/ocio.configs.0.7v4/nuke-default/config.ocio"); - -// OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); - - OCIO::ConstProcessorRcPtr processor = config->getProcessor("srgb", - OCIO::ROLE_SCENE_LINEAR); - OCIO::PackedImageDesc img(reinterpret_cast(f->data()), f->width(), f->height(), kRGBAChannels); processor->apply(img); diff --git a/app/render/colorservice.h b/app/render/colorservice.h index b132710d9..4cbf87cfd 100644 --- a/app/render/colorservice.h +++ b/app/render/colorservice.h @@ -1,8 +1,9 @@ #ifndef COLORSERVICE_H #define COLORSERVICE_H +#include #include -namespace OCIO = OCIO_NAMESPACE; +namespace OCIO = OCIO_NAMESPACE::v1; #include "decoder/frame.h" @@ -11,10 +12,12 @@ class ColorService public: ColorService(); - static void ConvertFrame(FramePtr f); + void ConvertFrame(FramePtr f); private: - + OCIO::ConstProcessorRcPtr processor; }; +using ColorServicePtr = std::shared_ptr; + #endif // COLORSERVICE_H diff --git a/app/render/gl/shadergenerators.cpp b/app/render/gl/shadergenerators.cpp index 65a0b2280..c5d263a0a 100644 --- a/app/render/gl/shadergenerators.cpp +++ b/app/render/gl/shadergenerators.cpp @@ -22,7 +22,9 @@ #include -ShaderPtr olive::gl::GetDefaultPipeline(const QString& function_name, const QString& shader_code) +namespace olive { + +ShaderPtr ShaderGenerator::DefaultPipeline(const QString& function_name, const QString& shader_code) { ShaderPtr program = std::make_shared(); @@ -88,10 +90,10 @@ ShaderPtr olive::gl::GetDefaultPipeline(const QString& function_name, const QStr frag_shader.append(shader_code); frag_shader.append(QString("\n" - "void main() {\n" - " vec4 color = %1(texture2D(texture, v_texcoord))*opacity;\n" - " gl_FragColor = color;\n" - "}\n").arg(function_name)); + "void main() {\n" + " vec4 color = %1(texture2D(texture, v_texcoord))*opacity;\n" + " gl_FragColor = color;\n" + "}\n").arg(function_name)); } @@ -111,7 +113,7 @@ ShaderPtr olive::gl::GetDefaultPipeline(const QString& function_name, const QStr return program; } -QString olive::gl::GetAlphaDisassociateFunction(const QString &function_name) +QString ShaderGenerator::AlphaDisassociateFunction(const QString &function_name) { return QString("vec4 %1(vec4 col) {\n" " if (col.a > 0.0) {\n" @@ -121,7 +123,7 @@ QString olive::gl::GetAlphaDisassociateFunction(const QString &function_name) "}\n").arg(function_name); } -QString olive::gl::GetAlphaReassociateFunction(const QString &function_name) +QString ShaderGenerator::AlphaReassociateFunction(const QString &function_name) { return QString("vec4 %1(vec4 col) {\n" " if (col.a > 0.0) {\n" @@ -131,7 +133,7 @@ QString olive::gl::GetAlphaReassociateFunction(const QString &function_name) "}\n").arg(function_name); } -QString olive::gl::GetAlphaAssociateFunction(const QString &function_name) +QString ShaderGenerator::AlphaAssociateFunction(const QString &function_name) { return QString("vec4 %1(vec4 col) {\n" " return vec4(col.rgb * col.a, col.a);\n" @@ -144,10 +146,10 @@ const int OCIO_LUT3D_EDGE_SIZE = 32; // copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE const int OCIO_NUM_3D_ENTRIES = 98304; -ShaderPtr olive::gl::GetOCIOPipeline(QOpenGLContext* ctx, - GLuint& lut_texture, - OCIO::ConstProcessorRcPtr processor, - bool alpha_is_associated) +ShaderPtr ShaderGenerator::OCIOPipeline(QOpenGLContext* ctx, + GLuint& lut_texture, + OCIO::ConstProcessorRcPtr processor, + bool alpha_is_associated) { QOpenGLExtraFunctions* xf = ctx->extraFunctions(); @@ -207,10 +209,10 @@ ShaderPtr olive::gl::GetOCIOPipeline(QOpenGLContext* ctx, shader_text.append("\n"); QString disassociate_func_name = "disassoc"; - shader_text.append(GetAlphaDisassociateFunction(disassociate_func_name)); + shader_text.append(AlphaDisassociateFunction(disassociate_func_name)); QString reassociate_func_name = "reassoc"; - shader_text.append(GetAlphaReassociateFunction(reassociate_func_name)); + shader_text.append(AlphaReassociateFunction(reassociate_func_name)); // Make OCIO call pass through disassociate and reassociate function shader_call = QString("%3(%1(%2(col), tex2));").arg(ocio_func_name, @@ -223,7 +225,7 @@ ShaderPtr olive::gl::GetOCIOPipeline(QOpenGLContext* ctx, // Add associate function QString associate_func_name = "assoc"; - shader_text.append(GetAlphaAssociateFunction(associate_func_name)); + shader_text.append(AlphaAssociateFunction(associate_func_name)); // Make OCIO call pass through associate function shader_call = QString("%2(%1(col, tex2));").arg(ocio_func_name, associate_func_name); @@ -241,10 +243,12 @@ ShaderPtr olive::gl::GetOCIOPipeline(QOpenGLContext* ctx, // Get pipeline-based shader to inject OCIO shader into - ShaderPtr shader = olive::gl::GetDefaultPipeline(process_function_name, shader_text); + ShaderPtr shader = ShaderGenerator::DefaultPipeline(process_function_name, shader_text); // Release LUT xf->glBindTexture(GL_TEXTURE_3D, 0); return shader; } + +} diff --git a/app/render/gl/shadergenerators.h b/app/render/gl/shadergenerators.h index acdf8ec51..7aad6f375 100644 --- a/app/render/gl/shadergenerators.h +++ b/app/render/gl/shadergenerators.h @@ -35,20 +35,21 @@ namespace OCIO = OCIO_NAMESPACE::v1; */ namespace olive { -namespace gl { -ShaderPtr GetDefaultPipeline(const QString &function_name = QString(), const QString &shader_code = QString()); +class ShaderGenerator { +public: + static ShaderPtr DefaultPipeline(const QString &function_name = QString(), const QString &shader_code = QString()); -ShaderPtr GetOCIOPipeline(QOpenGLContext *ctx, - GLuint &lut_texture, - OCIO::ConstProcessorRcPtr processor, - bool alpha_is_associated); + static ShaderPtr OCIOPipeline(QOpenGLContext *ctx, + GLuint &lut_texture, + OCIO::ConstProcessorRcPtr processor, + bool alpha_is_associated); -QString GetAlphaDisassociateFunction(const QString& function_name); -QString GetAlphaReassociateFunction(const QString& function_name); -QString GetAlphaAssociateFunction(const QString& function_name); + static QString AlphaDisassociateFunction(const QString& function_name); + static QString AlphaReassociateFunction(const QString& function_name); + static QString AlphaAssociateFunction(const QString& function_name); +}; -} } #endif // SHADERGENERATORS_H diff --git a/app/render/renderinstance.cpp b/app/render/renderinstance.cpp new file mode 100644 index 000000000..6bb9af876 --- /dev/null +++ b/app/render/renderinstance.cpp @@ -0,0 +1,79 @@ +/*** + + 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 "renderinstance.h" + +#include + +RenderInstance::RenderInstance(const int& width, + const int& height, + const olive::PixelFormat& format, + const olive::RenderMode& mode) : + width_(width), + height_(height), + format_(format), + mode_(mode) +{ +} + +bool RenderInstance::Start() +{ + // Create OpenGL context (automatically destroys any existing if there is one) + if (!ctx_.create()) { + qWarning() << tr("Failed to create OpenGL context in thread %1").arg(reinterpret_cast(this)); + return false; + } + + // Create offscreen surface + surface_.create(); + + // Make context current on that surface + if (!ctx_.makeCurrent(&surface_)) { + qWarning() << tr("Failed to makeCurrent() on offscreen surface in thread %1").arg(reinterpret_cast(this)); + surface_.destroy(); + return false; + } + + buffer_.Create(&ctx_, format_, width_, height_); + + return true; +} + +void RenderInstance::Stop() +{ + // Destroy buffer + buffer_.Destroy(); + + // Release OpenGL context + ctx_.doneCurrent(); + + // Destroy offscreen surface + surface_.destroy(); +} + +bool RenderInstance::IsStarted() +{ + return buffer_.IsCreated(); +} + +TextureBuffer *RenderInstance::buffer() +{ + return &buffer_; +} diff --git a/app/render/renderinstance.h b/app/render/renderinstance.h new file mode 100644 index 000000000..1578ceeed --- /dev/null +++ b/app/render/renderinstance.h @@ -0,0 +1,71 @@ +/*** + + 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 GLINSTANCE_H +#define GLINSTANCE_H + +#include +#include +#include + +#include "render/rendermodes.h" +#include "render/rendertypes.h" +#include "render/texturebuffer.h" + +/** + * @brief An object containing all resources necessary for each thread to support hardware accelerated rendering + * + * RenderInstance contains everything that Nodes will need to draw with on a per-thread basis. + * + * In OpenGL, + */ +class RenderInstance : public QObject +{ +public: + RenderInstance(const int& width, + const int& height, + const olive::PixelFormat& format, + const olive::RenderMode& mode); + + bool Start(); + + void Stop(); + + bool IsStarted(); + + TextureBuffer* buffer(); + +private: + QOpenGLContext ctx_; + + QOffscreenSurface surface_; + + TextureBuffer buffer_; + + int width_; + + int height_; + + olive::PixelFormat format_; + + olive::RenderMode mode_; +}; + +#endif // GLINSTANCE_H diff --git a/app/render/rendermodes.h b/app/render/rendermodes.h new file mode 100644 index 000000000..b13eb2fd3 --- /dev/null +++ b/app/render/rendermodes.h @@ -0,0 +1,25 @@ +#ifndef RENDERMODE_H +#define RENDERMODE_H + +namespace olive { + +/** + * @brief The primary different "modes" the renderer can function in + */ +enum RenderMode { + /** + * This render is for realtime preview ONLY and does not need to be "perfect". Nodes can use lower-accuracy functions + * to save performance when possible. + */ + kOffline, + + /** + * This render is some sort of export or master copy and Nodes should take time/bandwidth/system resources to produce + * a higher accuracy version. + */ + kOnline +}; + +} + +#endif // RENDERMODE_H diff --git a/app/render/rendertypes.h b/app/render/rendertypes.h new file mode 100644 index 000000000..e240ed903 --- /dev/null +++ b/app/render/rendertypes.h @@ -0,0 +1,8 @@ +#ifndef RENDERTYPES_H +#define RENDERTYPES_H + +#include + +using RenderTexture = GLuint; + +#endif // RENDERTYPES_H diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 9b847a4a6..0e2a00a79 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -383,8 +383,24 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (param_hitbox.contains(drop_item->mapFromScene(event->scenePos())) // See if we're dragging inside the hitbox && NodeParam::AreDataTypesCompatible(drag_src_param_, comp_param)) { // Make sure the types are compatible - drag_dest_param_ = comp_param; - end_point = drop_item->mapToScene(drop_item->GetParameterConnectorRect(i).center()); + // Prevent circular dependency - check if the Node we'll be outputting to already outputs to this Node + Node* outputting_node; + Node* receiving_node; + + // Determine which Node will be "submitting output" and which node will be "receiving input" + if (drag_src_param_->type() == NodeParam::kInput) { + receiving_node = drag_src_param_->parent(); + outputting_node = drop_item->node(); + } else { + receiving_node = drop_item->node(); + outputting_node = drag_src_param_->parent(); + } + + // Ensure the receiving node doesn't output to the outputting node + if (!receiving_node->OutputsTo(outputting_node)) { + drag_dest_param_ = comp_param; + end_point = drop_item->mapToScene(drop_item->GetParameterConnectorRect(i).center()); + } break; } @@ -408,8 +424,6 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) // Check if an edge drag was initiated if (dragging_edge_ != nullptr) { - // FIXME: Make this undoable - // Remove the drag object scene()->removeItem(dragging_edge_); @@ -424,17 +438,22 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) NodeEdgePtr new_edge; + NodeOutput* output; + NodeInput* input; + // Connecting will automatically add an edge UI object through the signal/slot system if (drag_dest_param_->type() == NodeParam::kOutput) { - new NodeEdgeAddCommand(static_cast(drag_dest_param_), - static_cast(drag_src_param_), - node_edge_change_command_); - + output = static_cast(drag_dest_param_); + input = static_cast(drag_src_param_); } else { - new NodeEdgeAddCommand(static_cast(drag_src_param_), - static_cast(drag_dest_param_), - node_edge_change_command_); + output = static_cast(drag_src_param_); + input = static_cast(drag_dest_param_); } + + // Use a command to make Node connecting undoable + new NodeEdgeAddCommand(output, + input, + node_edge_change_command_); } dragging_edge_ = nullptr; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 342a1461b..8b86da135 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -53,10 +53,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) : controls_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); layout->addWidget(controls_); - // FIXME: Test code - SetTimebase(rational(1001, 30000)); + // FIXME: Magic number ruler_->SetScale(48.0); - // End test code } void ViewerWidget::SetTimebase(const rational &r) diff --git a/app/widget/viewer/viewerglwidget.cpp b/app/widget/viewer/viewerglwidget.cpp index 17b57b82c..2a499edde 100644 --- a/app/widget/viewer/viewerglwidget.cpp +++ b/app/widget/viewer/viewerglwidget.cpp @@ -45,7 +45,7 @@ void ViewerGLWidget::SetTexture(GLuint tex) void ViewerGLWidget::initializeGL() { // Re-retrieve pipeline pertaining to this context - pipeline_ = olive::gl::GetDefaultPipeline(); + pipeline_ = olive::ShaderGenerator::DefaultPipeline(); } void ViewerGLWidget::paintGL()