foundations for background caching

This commit is contained in:
itsmattkc
2019-08-24 22:16:01 +10:00
parent 9439494c10
commit 438e7081f9
39 changed files with 919 additions and 213 deletions
+13
View File
@@ -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();
+13 -7
View File
@@ -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();
+1 -1
View File
@@ -8,7 +8,7 @@ class AlphaOverBlend : public BlendNode
public:
AlphaOverBlend();
public slots:
protected:
virtual void Process(const rational &time) override;
};
+40 -24
View File
@@ -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
View File
@@ -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);
};
+1 -1
View File
@@ -42,7 +42,7 @@ public:
NodeInput* texture_input();
public slots:
protected:
virtual void Process(const rational &time) override;
private:
+1 -1
View File
@@ -41,7 +41,7 @@ public:
NodeOutput* texture_output();
public slots:
protected:
virtual void Process(const rational &time) override;
private:
+33 -11
View File
@@ -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
}
+8 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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_;
+10 -3
View File
@@ -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)
+5 -1
View File
@@ -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
+43 -22
View File
@@ -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;
}
+11 -1
View File
@@ -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_;
+12 -2
View File
@@ -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_);
}
}
+5 -1
View File
@@ -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
+170 -12
View File
@@ -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_;
+51 -3
View File
@@ -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
+46 -33
View File
@@ -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);
}
+27 -14
View File
@@ -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>;
+5
View File
@@ -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);
+2
View File
@@ -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
+4 -4
View File
@@ -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 -7
View File
@@ -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);
+6 -3
View File
@@ -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
+20 -16
View File
@@ -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;
}
}
+11 -10
View File
@@ -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
+79
View File
@@ -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_;
}
+71
View File
@@ -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
+25
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
#ifndef RENDERTYPES_H
#define RENDERTYPES_H
#include <QOpenGLFunctions>
using RenderTexture = GLuint;
#endif // RENDERTYPES_H
+30 -11
View File
@@ -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;
+1 -3
View File
@@ -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)
+1 -1
View File
@@ -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()