Files
oak-editor/app/node/output/track/track.cpp
T
itsmattkc 53ae25a7cb nodes: optimize node graph changes by only updating from inputs that changed
A huge optimization that ensures only the parts of a node graph that have
changed get pushed to the renderer. For thread-safety, the node graph is
copied elsewhere so that users can make changes asynchronously and the graph
can update when its threads are ready. Up until now, if an input value changed,
every node's values would be re-copied, or worse, if a connection was changed,
the entire graph would be recopied. This has been negligible in testing since
we've been largely testing with small graphs, but for massive projects, it's
important that this be as optimized as possible.
2020-04-26 04:16:15 +10:00

590 lines
13 KiB
C++

/***
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 "track.h"
#include <QApplication>
#include <QDebug>
#include <QFontMetrics>
#include "node/block/gap/gap.h"
#include "node/graph.h"
OLIVE_NAMESPACE_ENTER
TrackOutput::TrackOutput() :
track_type_(Timeline::kTrackTypeNone),
block_invalidate_cache_stack_(0),
index_(-1),
locked_(false)
{
block_input_ = new NodeInputArray("block_in", NodeParam::kAny);
block_input_->set_is_keyframable(false);
AddInput(block_input_);
connect(block_input_, &NodeInputArray::SubParamEdgeAdded, this, &TrackOutput::BlockConnected);
connect(block_input_, &NodeInputArray::SubParamEdgeRemoved, this, &TrackOutput::BlockDisconnected);
connect(block_input_, &NodeInputArray::SizeChanged, this, &TrackOutput::BlockListSizeChanged);
muted_input_ = new NodeInput("muted_in", NodeParam::kBoolean);
muted_input_->set_is_keyframable(false);
connect(muted_input_, &NodeInput::ValueChanged, this, &TrackOutput::MutedInputValueChanged);
AddInput(muted_input_);
// Set default height
track_height_ = GetDefaultTrackHeight();
}
void TrackOutput::set_track_type(const Timeline::TrackType &track_type)
{
track_type_ = track_type;
}
const Timeline::TrackType& TrackOutput::track_type()
{
return track_type_;
}
Block::Type TrackOutput::type() const
{
return kClip;
}
Block *TrackOutput::copy() const
{
return new TrackOutput();
}
QString TrackOutput::Name() const
{
return tr("Track");
}
QString TrackOutput::id() const
{
return QStringLiteral("org.olivevideoeditor.Olive.track");
}
QString TrackOutput::Category() const
{
return tr("Output");
}
QString TrackOutput::Description() const
{
return tr("Node for representing and processing a single array of Blocks sorted by time. Also represents the end of "
"a Sequence.");
}
QString TrackOutput::GetTrackName()
{
if (track_name_.isEmpty()) {
return GetDefaultTrackName(track_type_, index_);
}
return track_name_;
}
const int &TrackOutput::GetTrackHeight() const
{
return track_height_;
}
void TrackOutput::SetTrackHeight(const int &height)
{
track_height_ = height;
emit TrackHeightChanged(track_height_);
}
void TrackOutput::Retranslate()
{
Block::Retranslate();
block_input_->set_name(tr("Blocks"));
muted_input_->set_name(tr("Muted"));
}
const int &TrackOutput::Index()
{
return index_;
}
void TrackOutput::SetIndex(const int &index)
{
index_ = index;
}
Block *TrackOutput::BlockContainingTime(const rational &time) const
{
foreach (Block* block, block_cache_) {
if (block->in() < time && block->out() > time) {
return block;
} else if (block->out() == time) {
break;
}
}
return nullptr;
}
Block *TrackOutput::NearestBlockBefore(const rational &time) const
{
foreach (Block* block, block_cache_) {
// Blocks are sorted by time, so the first Block who's out point is at/after this time is the correct Block
if (block->out() >= time) {
return block;
}
}
return nullptr;
}
Block *TrackOutput::NearestBlockBeforeOrAt(const rational &time) const
{
foreach (Block* block, block_cache_) {
// Blocks are sorted by time, so the first Block who's out point is at/after this time is the correct Block
if (block->out() > time) {
return block;
}
}
return nullptr;
}
Block *TrackOutput::NearestBlockAfterOrAt(const rational &time) const
{
foreach (Block* block, block_cache_) {
// Blocks are sorted by time, so the first Block after this time is the correct Block
if (block->in() >= time) {
return block;
}
}
return nullptr;
}
Block *TrackOutput::NearestBlockAfter(const rational &time) const
{
foreach (Block* block, block_cache_) {
// Blocks are sorted by time, so the first Block after this time is the correct Block
if (block->in() > time) {
return block;
}
}
return nullptr;
}
Block *TrackOutput::BlockAtTime(const rational &time) const
{
if (IsMuted()) {
return nullptr;
}
foreach (Block* block, block_cache_) {
if (block
&& block->in() <= time
&& block->out() > time) {
if (block->is_enabled()) {
return block;
} else {
break;
}
}
}
return nullptr;
}
QList<Block *> TrackOutput::BlocksAtTimeRange(const TimeRange &range) const
{
QList<Block*> list;
if (IsMuted()) {
return list;
}
foreach (Block* block, block_cache_) {
if (block
&& block->out() > range.in()
&& block->in() < range.out()) {
list.append(block);
}
}
return list;
}
const QVector<Block *> &TrackOutput::Blocks() const
{
return block_cache_;
}
void TrackOutput::InvalidateCache(const TimeRange &range, NodeInput *from, NodeInput *source)
{
if (block_invalidate_cache_stack_ == 0) {
Node::InvalidateCache(TimeRange(qMax(range.in(), rational(0)), qMin(range.out(), track_length())), from, source);
}
}
void TrackOutput::InsertBlockBefore(Block* block, Block* after)
{
InsertBlockAtIndex(block, block_cache_.indexOf(after));
}
void TrackOutput::InsertBlockAfter(Block *block, Block *before)
{
int before_index = block_cache_.indexOf(before);
Q_ASSERT(before_index >= 0);
if (before_index == block_cache_.size() - 1) {
AppendBlock(block);
} else {
InsertBlockAtIndex(block, before_index + 1);
}
}
void TrackOutput::PrependBlock(Block *block)
{
InsertBlockAtIndex(block, 0);
}
void TrackOutput::InsertBlockAtIndex(Block *block, int index)
{
BlockInvalidateCache();
block_input_->InsertAt(index);
NodeParam::ConnectEdge(block->output(),
block_input_->At(index));
UnblockInvalidateCache();
InvalidateCache(TimeRange(block->in(), track_length()), block_input_, block_input_);
}
void TrackOutput::AppendBlock(Block *block)
{
BlockInvalidateCache();
int last_index = block_input_->GetSize();
block_input_->Append();
NodeParam::ConnectEdge(block->output(),
block_input_->At(last_index));
UnblockInvalidateCache();
// Invalidate area that block was added to
InvalidateCache(TimeRange(block->in(), track_length()), block_input_, block_input_);
}
void TrackOutput::BlockInvalidateCache()
{
block_invalidate_cache_stack_++;
}
void TrackOutput::UnblockInvalidateCache()
{
block_invalidate_cache_stack_--;
}
void TrackOutput::RippleRemoveBlock(Block *block)
{
BlockInvalidateCache();
rational remove_in = block->in();
int index_of_block_to_remove = block_cache_.indexOf(block);
block_input_->RemoveAt(index_of_block_to_remove);
UnblockInvalidateCache();
InvalidateCache(TimeRange(remove_in, track_length()), block_input_, block_input_);
}
void TrackOutput::ReplaceBlock(Block *old, Block *replace)
{
BlockInvalidateCache();
int index_of_old_block = block_cache_.indexOf(old);
NodeParam::DisconnectEdge(old->output(),
block_input_->At(index_of_old_block));
NodeParam::ConnectEdge(replace->output(),
block_input_->At(index_of_old_block));
UnblockInvalidateCache();
if (old->length() == replace->length()) {
InvalidateCache(TimeRange(replace->in(), replace->out()), block_input_, block_input_);
} else {
InvalidateCache(TimeRange(replace->in(), RATIONAL_MAX), block_input_, block_input_);
}
}
TrackOutput *TrackOutput::TrackFromBlock(const Block *block)
{
NodeOutput* output = block->output();
foreach (NodeEdgePtr edge, output->edges()) {
Node* n = edge->input()->parentNode();
if (n->IsTrack()) {
return static_cast<TrackOutput*>(n);
}
}
return nullptr;
}
const rational &TrackOutput::track_length() const
{
return track_length_;
}
bool TrackOutput::IsTrack() const
{
return true;
}
int TrackOutput::GetTrackHeightIncrement()
{
return qApp->fontMetrics().height() / 2;
}
int TrackOutput::GetDefaultTrackHeight()
{
return qApp->fontMetrics().height() * 3;
}
int TrackOutput::GetTrackHeightMinimum()
{
return qApp->fontMetrics().height() * 3 / 2;
}
QString TrackOutput::GetDefaultTrackName(Timeline::TrackType type, int index)
{
// Starts tracks at 1 rather than 0
int user_friendly_index = index+1;
switch (type) {
case Timeline::kTrackTypeVideo: return tr("Video %1").arg(user_friendly_index);
case Timeline::kTrackTypeAudio: return tr("Audio %1").arg(user_friendly_index);
case Timeline::kTrackTypeSubtitle: return tr("Subtitle %1").arg(user_friendly_index);
case Timeline::kTrackTypeNone:
case Timeline::kTrackTypeCount:
break;
}
return tr("Track %1").arg(user_friendly_index);
}
bool TrackOutput::IsMuted() const
{
return muted_input_->get_standard_value().toBool();
}
bool TrackOutput::IsLocked() const
{
return locked_;
}
NodeInputArray *TrackOutput::block_input() const
{
return block_input_;
}
void TrackOutput::SetTrackName(const QString &name)
{
track_name_ = name;
}
void TrackOutput::SetMuted(bool e)
{
muted_input_->set_standard_value(e);
InvalidateCache(TimeRange(0, track_length()), block_input_, block_input_);
}
void TrackOutput::SetLocked(bool e)
{
locked_ = e;
}
void TrackOutput::UpdateInOutFrom(int index)
{
Q_ASSERT(index >= 0);
Q_ASSERT(index < block_cache_.size());
rational new_track_length;
// Find block just before this one to find the last out point
for (int i=index-1;i>=0;i--) {
Block* b = block_cache_.at(i);
if (b) {
new_track_length = b->out();
break;
}
}
for (int i=index;i<block_cache_.size();i++) {
Block* b = block_cache_.at(i);
if (b) {
// Set in
b->set_in(new_track_length);
// Set out
new_track_length += b->length();
b->set_out(new_track_length);
emit b->Refreshed();
}
}
// Update track length
if (new_track_length != track_length_) {
rational old_track_length = track_length_;
track_length_ = new_track_length;
emit TrackLengthChanged();
InvalidateCache(TimeRange(qMin(old_track_length, new_track_length), qMax(old_track_length, new_track_length)),
block_input_,
block_input_);
}
}
void TrackOutput::UpdatePreviousAndNextOfIndex(int index)
{
Block* ref = block_cache_.at(index);
Block* previous = nullptr;
Block* next = nullptr;
// Find previous
for (int i=index-1;i>=0;i--) {
previous = block_cache_.at(i);
if (previous) {
break;
}
}
// Find next
for (int i=index+1;i<block_cache_.size();i++) {
next = block_cache_.at(i);
if (next) {
break;
}
}
if (ref) {
// Link blocks together
ref->set_previous(previous);
ref->set_next(next);
if (previous)
previous->set_next(ref);
if (next)
next->set_previous(ref);
} else {
// Link previous and next together
if (previous)
previous->set_next(next);
if (next)
next->set_previous(previous);
}
}
void TrackOutput::BlockConnected(NodeEdgePtr edge)
{
int block_index = block_input_->IndexOfSubParameter(edge->input());
Q_ASSERT(block_index >= 0);
Node* connected_node = edge->output()->parentNode();
Block* connected_block = connected_node->IsBlock() ? static_cast<Block*>(connected_node) : nullptr;
block_cache_.replace(block_index, connected_block);
UpdatePreviousAndNextOfIndex(block_index);
UpdateInOutFrom(block_index);
if (connected_block) {
connect(connected_block, SIGNAL(LengthChanged(const rational&)), this, SLOT(BlockLengthChanged()));
emit BlockAdded(connected_block);
}
}
void TrackOutput::BlockDisconnected(NodeEdgePtr edge)
{
int block_index = block_input_->IndexOfSubParameter(edge->input());
Q_ASSERT(block_index >= 0);
block_cache_.replace(block_index, nullptr);
UpdatePreviousAndNextOfIndex(block_index);
UpdateInOutFrom(block_index);
Node* connected_node = edge->output()->parentNode();
Block* connected_block = connected_node->IsBlock() ? static_cast<Block*>(connected_node) : nullptr;
if (connected_block) {
disconnect(connected_block, SIGNAL(LengthChanged(const rational&)), this, SLOT(BlockLengthChanged()));
// Update previous and next references
emit BlockRemoved(connected_block);
}
}
void TrackOutput::BlockListSizeChanged(int size)
{
int old_size = block_cache_.size();
block_cache_.resize(size);
// Fill new slots with nullptr
for (int i=old_size;i<size;i++) {
block_cache_.replace(i, nullptr);
}
}
void TrackOutput::BlockLengthChanged()
{
// Assumes sender is a Block
Block* b = static_cast<Block*>(sender());
int index = block_cache_.indexOf(b);
Q_ASSERT(index >= 0);
UpdateInOutFrom(index);
}
void TrackOutput::MutedInputValueChanged()
{
emit MutedChanged(IsMuted());
}
OLIVE_NAMESPACE_EXIT