foundations for background caching
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -8,7 +8,7 @@ class AlphaOverBlend : public BlendNode
|
||||
public:
|
||||
AlphaOverBlend();
|
||||
|
||||
public slots:
|
||||
protected:
|
||||
virtual void Process(const rational &time) override;
|
||||
};
|
||||
|
||||
|
||||
+40
-24
@@ -22,16 +22,13 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
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<Block>(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<Block*>(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<Block*>(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<Node *> Block::GetImmediateDependenciesAt(const rational &time)
|
||||
{
|
||||
Q_UNUSED(time)
|
||||
|
||||
QList<Node *> nodes = Node::GetImmediateDependencies();
|
||||
|
||||
// Swap attached block for current block at this time
|
||||
nodes.removeAll(previous());
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
+13
-7
@@ -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<Node *> 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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
NodeInput* texture_input();
|
||||
|
||||
public slots:
|
||||
protected:
|
||||
virtual void Process(const rational &time) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
|
||||
NodeOutput* texture_output();
|
||||
|
||||
public slots:
|
||||
protected:
|
||||
virtual void Process(const rational &time) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -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>(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<olive::PixelFormat>(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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+62
-8
@@ -20,9 +20,12 @@
|
||||
|
||||
#include "node.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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<NodeParam *> params = parameters();
|
||||
|
||||
// Loop through all parameters (there should be no children that are not NodeParams)
|
||||
for (int i=0;i<params.size();i++) {
|
||||
NodeParam* param = params.at(i);
|
||||
foreach (NodeParam* param, params) {
|
||||
|
||||
// If the Node is an output, relay the signal to any Nodes that are connected to it
|
||||
if (param->type() == NodeParam::kOutput) {
|
||||
for (int i=0;i<param->edges().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<NodeParam*>(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<Node*>& list) {
|
||||
void GetDependenciesInternal(Node* n, QList<Node*>& list, bool traverse) {
|
||||
QList<NodeParam*> params = n->parameters();
|
||||
|
||||
foreach (NodeParam* p, params) {
|
||||
@@ -109,7 +144,10 @@ void GetDependenciesInternal(Node* n, QList<Node*>& 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 *> Node::GetDependencies()
|
||||
{
|
||||
QList<Node *> node_list;
|
||||
|
||||
GetDependenciesInternal(this, node_list);
|
||||
GetDependenciesInternal(this, node_list, true);
|
||||
|
||||
return node_list;
|
||||
}
|
||||
@@ -159,6 +197,22 @@ QList<Node *> Node::GetExclusiveDependencies()
|
||||
return deps;
|
||||
}
|
||||
|
||||
QList<Node *> Node::GetImmediateDependencies()
|
||||
{
|
||||
QList<Node *> node_list;
|
||||
|
||||
GetDependenciesInternal(this, node_list, false);
|
||||
|
||||
return node_list;
|
||||
}
|
||||
|
||||
QList<Node *> Node::GetImmediateDependenciesAt(const rational &time)
|
||||
{
|
||||
Q_UNUSED(time)
|
||||
|
||||
return GetImmediateDependencies();
|
||||
}
|
||||
|
||||
bool Node::OutputsTo(Node *n)
|
||||
{
|
||||
QList<NodeParam*> params = parameters();
|
||||
|
||||
+32
-1
@@ -21,6 +21,7 @@
|
||||
#ifndef NODE_H
|
||||
#define NODE_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
#include "common/rational.h"
|
||||
@@ -121,6 +122,18 @@ public:
|
||||
*/
|
||||
QList<Node*> GetExclusiveDependencies();
|
||||
|
||||
/**
|
||||
* @brief Retrieve immediate dependencies (only nodes that are directly connected to the inputs of this one)
|
||||
*/
|
||||
QList<Node*> 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<Node*> 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<NodeInput*> ignore_invalid_cache_inputs_;
|
||||
|
||||
rational last_time_;
|
||||
|
||||
QMutex lock_;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
|
||||
+1
-1
@@ -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_;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<TrackOutput*> track_cache_;
|
||||
|
||||
rational timebase_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Slot for when the track connection is added
|
||||
|
||||
@@ -99,6 +99,21 @@ void TrackOutput::Refresh()
|
||||
Block::Refresh();
|
||||
}
|
||||
|
||||
QList<Node *> TrackOutput::GetImmediateDependenciesAt(const rational &time)
|
||||
{
|
||||
QList<Node *> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,11 @@ public:
|
||||
|
||||
virtual void Refresh() override;
|
||||
|
||||
/**
|
||||
* @brief Override swaps "attached block" with "current block"
|
||||
*/
|
||||
virtual QList<Node*> 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*> block_cache_;
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,10 +20,19 @@
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QImage>
|
||||
#include <QtMath>
|
||||
|
||||
#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>();
|
||||
//GLuint tex = texture_input_->get_value(time).value<GLuint>();
|
||||
|
||||
//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_.size();i++) {
|
||||
threads_[i] = std::make_shared<RendererThread>();
|
||||
threads_[i]->run();
|
||||
threads_[i] = std::make_shared<RendererThread>(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;i<threads_.size();i++) {
|
||||
RendererThreadPtr thread = threads_.at(i);
|
||||
|
||||
if (thread->Queue(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<RendererThread*>(QThread::currentThread());
|
||||
}
|
||||
|
||||
RenderInstance *RendererProcessor::CurrentInstance()
|
||||
{
|
||||
RendererThread* thread = CurrentThread();
|
||||
|
||||
if (thread != nullptr) {
|
||||
return thread->render_instance();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeInput *RendererProcessor::texture_input()
|
||||
{
|
||||
return texture_input_;
|
||||
|
||||
@@ -21,7 +21,11 @@
|
||||
#ifndef RENDERER_H
|
||||
#define RENDERER_H
|
||||
|
||||
#include <QOpenGLTexture>
|
||||
|
||||
#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<RendererThreadPtr> 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<rational> cache_queue_;
|
||||
QString cache_name_;
|
||||
qint64 cache_time_;
|
||||
QString cache_id_;
|
||||
|
||||
};
|
||||
|
||||
#endif // RENDERER_H
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "rendererprobe.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
RendererProbe::RendererProbe()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RendererProbe::ProbeNode(Node *node, int thread_count, const rational& time)
|
||||
{
|
||||
Q_ASSERT(thread_count > 0);
|
||||
|
||||
//QVector< QVector<Node*> > 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<Node*> deps = node->GetImmediateDependenciesAt(time);
|
||||
|
||||
foreach (Node* dep, deps) {
|
||||
qDebug() << " Dependency found:" << dep;
|
||||
}
|
||||
|
||||
foreach (Node* dep, deps) {
|
||||
TraverseNode(dep, thread, time);
|
||||
thread++;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -22,8 +22,13 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
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<quintptr>(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<quintptr>(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);
|
||||
}
|
||||
|
||||
@@ -23,36 +23,36 @@
|
||||
|
||||
#include <memory>
|
||||
#include <QMutex>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#include <QThread>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#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<RenderQueueEntry> queue_;
|
||||
|
||||
};
|
||||
|
||||
using RendererThreadPtr = std::shared_ptr<RendererThread>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<float*>(f->data()), f->width(), f->height(), kRGBAChannels);
|
||||
|
||||
processor->apply(img);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#ifndef COLORSERVICE_H
|
||||
#define COLORSERVICE_H
|
||||
|
||||
#include <memory>
|
||||
#include <OpenColorIO/OpenColorIO.h>
|
||||
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<ColorService>;
|
||||
|
||||
#endif // COLORSERVICE_H
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
|
||||
#include <QOpenGLExtraFunctions>
|
||||
|
||||
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<QOpenGLShaderProgram>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "renderinstance.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
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<quintptr>(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<quintptr>(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_;
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef GLINSTANCE_H
|
||||
#define GLINSTANCE_H
|
||||
|
||||
#include <QMatrix4x4>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
|
||||
#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
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef RENDERTYPES_H
|
||||
#define RENDERTYPES_H
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
|
||||
using RenderTexture = GLuint;
|
||||
|
||||
#endif // RENDERTYPES_H
|
||||
@@ -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<NodeOutput*>(drag_dest_param_),
|
||||
static_cast<NodeInput*>(drag_src_param_),
|
||||
node_edge_change_command_);
|
||||
|
||||
output = static_cast<NodeOutput*>(drag_dest_param_);
|
||||
input = static_cast<NodeInput*>(drag_src_param_);
|
||||
} else {
|
||||
new NodeEdgeAddCommand(static_cast<NodeOutput*>(drag_src_param_),
|
||||
static_cast<NodeInput*>(drag_dest_param_),
|
||||
node_edge_change_command_);
|
||||
output = static_cast<NodeOutput*>(drag_src_param_);
|
||||
input = static_cast<NodeInput*>(drag_dest_param_);
|
||||
}
|
||||
|
||||
// Use a command to make Node connecting undoable
|
||||
new NodeEdgeAddCommand(output,
|
||||
input,
|
||||
node_edge_change_command_);
|
||||
}
|
||||
|
||||
dragging_edge_ = nullptr;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user