transitions: extended transition logic

General code and functionality improvements. Moved more code out of the
OpenGL backend for portability. Implemented audio transitions.
Implemented basic transition animation curve settings.
This commit is contained in:
itsmattkc
2020-07-16 00:36:21 +10:00
parent de74adeb5e
commit 662d66ad10
10 changed files with 224 additions and 52 deletions
@@ -59,4 +59,31 @@ ShaderCode CrossDissolveTransition::GetShaderCode(const QString &shader_id) cons
return ShaderCode(Node::ReadFileAsString(":/shaders/crossdissolve.frag"), QString());
}
void CrossDissolveTransition::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const
{
for (int i=0; i<out_samples->sample_count(); i++) {
double this_sample_time = out_samples->audio_params().samples_to_time(i).toDouble() + time_in;
double progress = GetTotalProgress(this_sample_time);
for (int j=0; j<out_samples->audio_params().channel_count(); j++) {
out_samples->data()[j][i] = 0;
if (from_samples) {
if (i < from_samples->sample_count()) {
out_samples->data()[j][i] += from_samples->data()[j][i] * TransformCurve(1.0 - progress);
}
}
if (to_samples) {
// Offset input samples from the end
int in_index = i - (out_samples->sample_count() - to_samples->sample_count());
if (in_index >= 0) {
out_samples->data()[j][i] += to_samples->data()[j][in_index] * TransformCurve(progress);
}
}
}
}
}
OLIVE_NAMESPACE_EXIT
@@ -41,6 +41,9 @@ public:
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
protected:
virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const;
};
OLIVE_NAMESPACE_EXIT
+111 -16
View File
@@ -28,17 +28,22 @@ TransitionBlock::TransitionBlock() :
connected_out_block_(nullptr),
connected_in_block_(nullptr)
{
out_block_input_ = new NodeInput("out_block_in", NodeParam::kBuffer);
out_block_input_ = new NodeInput(QStringLiteral("out_block_in"), NodeParam::kBuffer);
out_block_input_->set_is_keyframable(false);
connect(out_block_input_, &NodeParam::EdgeAdded, this, &TransitionBlock::BlockConnected);
connect(out_block_input_, &NodeParam::EdgeRemoved, this, &TransitionBlock::BlockDisconnected);
AddInput(out_block_input_);
in_block_input_ = new NodeInput("in_block_in", NodeParam::kBuffer);
in_block_input_ = new NodeInput(QStringLiteral("in_block_in"), NodeParam::kBuffer);
in_block_input_->set_is_keyframable(false);
connect(in_block_input_, &NodeParam::EdgeAdded, this, &TransitionBlock::BlockConnected);
connect(in_block_input_, &NodeParam::EdgeRemoved, this, &TransitionBlock::BlockDisconnected);
AddInput(in_block_input_);
curve_input_ = new NodeInput(QStringLiteral("curve_in"), NodeParam::kCombo);
curve_input_->set_is_keyframable(false);
curve_input_->set_connectable(false);
AddInput(curve_input_);
}
Block::Type TransitionBlock::type() const
@@ -62,6 +67,10 @@ void TransitionBlock::Retranslate()
out_block_input_->set_name(tr("From"));
in_block_input_->set_name(tr("To"));
curve_input_->set_name(tr("Curve"));
// These must correspond to the CurveType enum
curve_input_->set_combobox_strings({ tr("Linear"), tr("Exponential"), tr("Logarithmic") });
}
rational TransitionBlock::in_offset() const
@@ -106,12 +115,12 @@ Block *TransitionBlock::connected_in_block() const
return connected_in_block_;
}
double TransitionBlock::GetTotalProgress(const rational &time) const
double TransitionBlock::GetTotalProgress(const double &time) const
{
return GetInternalTransitionTime(time) / length().toDouble();
}
double TransitionBlock::GetOutProgress(const rational &time) const
double TransitionBlock::GetOutProgress(const double &time) const
{
if (out_offset() == 0) {
return 0;
@@ -120,7 +129,7 @@ double TransitionBlock::GetOutProgress(const rational &time) const
return clamp(1.0 - (GetInternalTransitionTime(time) / out_offset().toDouble()), 0.0, 1.0);
}
double TransitionBlock::GetInProgress(const rational &time) const
double TransitionBlock::GetInProgress(const double &time) const
{
if (in_offset() == 0) {
return 0;
@@ -133,18 +142,34 @@ void TransitionBlock::Hash(QCryptographicHash &hash, const rational &time) const
{
Node::Hash(hash, time);
double all_prog = GetTotalProgress(time);
double in_prog = GetInProgress(time);
double out_prog = GetOutProgress(time);
double time_dbl = time.toDouble();
double all_prog = GetTotalProgress(time_dbl);
double in_prog = GetInProgress(time_dbl);
double out_prog = GetOutProgress(time_dbl);
hash.addData(reinterpret_cast<const char*>(&all_prog), sizeof(double));
hash.addData(reinterpret_cast<const char*>(&in_prog), sizeof(double));
hash.addData(reinterpret_cast<const char*>(&out_prog), sizeof(double));
}
double TransitionBlock::GetInternalTransitionTime(const rational &time) const
double TransitionBlock::GetInternalTransitionTime(const double &time) const
{
return time.toDouble() - in().toDouble();
return time - in().toDouble();
}
void TransitionBlock::InsertTransitionTimes(AcceleratedJob *job, const double &time) const
{
// Provides total transition progress from 0.0 (start) - 1.0 (end)
job->InsertValue(QStringLiteral("ove_tprog_all"),
NodeValue(NodeParam::kFloat, GetTotalProgress(time), this));
// Provides progress of out section from 1.0 (start) - 0.0 (end)
job->InsertValue(QStringLiteral("ove_tprog_out"),
NodeValue(NodeParam::kFloat, GetOutProgress(time), this));
// Provides progress of in section from 0.0 (start) - 1.0 (end)
job->InsertValue(QStringLiteral("ove_tprog_in"),
NodeValue(NodeParam::kFloat, GetInProgress(time), this));
}
void TransitionBlock::BlockConnected(NodeEdgePtr edge)
@@ -173,16 +198,62 @@ void TransitionBlock::BlockDisconnected(NodeEdgePtr edge)
NodeValueTable TransitionBlock::Value(NodeValueDatabase &value) const
{
ShaderJob job;
NodeParam::DataType data_type;
job.InsertValue(out_block_input(), value);
job.InsertValue(in_block_input(), value);
job.SetAlphaChannelRequired(true);
if (out_block_input()->is_connected()) {
data_type = value[out_block_input()].GetWithMeta(NodeParam::kBuffer).type();
} else if (in_block_input()->is_connected()) {
data_type = value[in_block_input()].GetWithMeta(NodeParam::kBuffer).type();
} else {
data_type = NodeParam::kNone;
}
ShaderJobEvent(value, job);
NodeParam::DataType job_type;
QVariant push_job;
if (data_type == NodeParam::kTexture) {
// This must be a visual transition
ShaderJob job;
job.InsertValue(out_block_input(), value);
job.InsertValue(in_block_input(), value);
job.InsertValue(curve_input_, value);
job.SetAlphaChannelRequired(true);
double time = value[QStringLiteral("global")].Get(NodeParam::kFloat, QStringLiteral("time_in")).toDouble();
InsertTransitionTimes(&job, time);
ShaderJobEvent(value, job);
job_type = NodeParam::kShaderJob;
push_job = QVariant::fromValue(job);
} else if (data_type == NodeParam::kSamples) {
// This must be an audio transition
SampleBufferPtr from_samples = value[out_block_input()].Take(NodeParam::kBuffer).value<SampleBufferPtr>();
SampleBufferPtr to_samples = value[in_block_input()].Take(NodeParam::kBuffer).value<SampleBufferPtr>();
if (from_samples || to_samples) {
double time_in = value[QStringLiteral("global")].Get(NodeParam::kFloat, QStringLiteral("time_in")).toDouble();
double time_out = value[QStringLiteral("global")].Get(NodeParam::kFloat, QStringLiteral("time_out")).toDouble();
const AudioParams& params = (from_samples) ? from_samples->audio_params() : to_samples->audio_params();
int nb_samples = params.time_to_samples(time_out - time_in);
SampleBufferPtr out_samples = SampleBuffer::CreateAllocated(params, nb_samples);
SampleJobEvent(from_samples, to_samples, out_samples, time_in);
job_type = NodeParam::kSamples;
push_job = QVariant::fromValue(out_samples);
}
}
NodeValueTable table = value.Merge();
table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this);
if (!push_job.isNull()) {
table.Push(job_type, push_job, this);
}
return table;
}
@@ -192,4 +263,28 @@ void TransitionBlock::ShaderJobEvent(NodeValueDatabase &value, ShaderJob &job) c
Q_UNUSED(job)
}
void TransitionBlock::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const
{
Q_UNUSED(from_samples)
Q_UNUSED(to_samples)
Q_UNUSED(out_samples)
Q_UNUSED(time_in)
}
double TransitionBlock::TransformCurve(double linear) const
{
switch (static_cast<CurveType>(curve_input_->get_standard_value().toInt())) {
case kLinear:
break;
case kExponential:
linear *= linear;
break;
case kLogarithmic:
linear = qSqrt(linear);
break;
}
return linear;
}
OLIVE_NAMESPACE_EXIT
+18 -4
View File
@@ -43,9 +43,9 @@ public:
Block* connected_out_block() const;
Block* connected_in_block() const;
double GetTotalProgress(const rational& time) const;
double GetOutProgress(const rational& time) const;
double GetInProgress(const rational& time) const;
double GetTotalProgress(const double &time) const;
double GetOutProgress(const double &time) const;
double GetInProgress(const double &time) const;
virtual void Hash(QCryptographicHash& hash, const rational &time) const override;
@@ -54,13 +54,27 @@ public:
protected:
virtual void ShaderJobEvent(NodeValueDatabase &value, ShaderJob& job) const;
virtual void SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const;
double TransformCurve(double linear) const;
private:
double GetInternalTransitionTime(const rational& time) const;
enum CurveType {
kLinear,
kExponential,
kLogarithmic
};
double GetInternalTransitionTime(const double &time) const;
void InsertTransitionTimes(AcceleratedJob* job, const double& time) const;
NodeInput* out_block_input_;
NodeInput* in_block_input_;
NodeInput* curve_input_;
Block* connected_out_block_;
Block* connected_in_block_;
+10 -5
View File
@@ -41,11 +41,7 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa
database.Insert(input, ProcessInput(input, input_time));
}
// Insert global variables
NodeValueTable global;
global.Push(NodeParam::kFloat, range.in().toDouble(), nullptr, QStringLiteral("time_in"));
global.Push(NodeParam::kFloat, range.out().toDouble(), nullptr, QStringLiteral("time_out"));
database.Insert(QStringLiteral("global"), global);
AddGlobalsToDatabase(database, range);
return database;
}
@@ -156,6 +152,15 @@ QVariant NodeTraverser::GetCachedFrame(const Node *node, const rational &time)
return QVariant();
}
void NodeTraverser::AddGlobalsToDatabase(NodeValueDatabase &db, const TimeRange& range)
{
// Insert global variables
NodeValueTable global;
global.Push(NodeParam::kFloat, range.in().toDouble(), nullptr, QStringLiteral("time_in"));
global.Push(NodeParam::kFloat, range.out().toDouble(), nullptr, QStringLiteral("time_out"));
db.Insert(QStringLiteral("global"), global);
}
void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params)
{
bool got_cached_frame = false;
+2
View File
@@ -56,6 +56,8 @@ protected:
virtual QVariant GetCachedFrame(const Node *node, const rational &time);
static void AddGlobalsToDatabase(NodeValueDatabase& db, const TimeRange &range);
private:
void PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params);
+10 -20
View File
@@ -279,18 +279,21 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
NodeValueMap::const_iterator it;
for (it=job.GetValues().constBegin(); it!=job.GetValues().constEnd(); it++) {
// See if the shader has takes this parameter as an input
int variable_location = shader->uniformLocation(it.key()->id());
int variable_location = shader->uniformLocation(it.key());
if (variable_location == -1) {
continue;
}
// See if this value corresponds to an input (NOTE: it may not and this may be null)
NodeInput* corresponding_input = node->GetInputWithID(it.key());
// This variable is used in the shader, let's set it
const QVariant& value = it.value().data();
NodeParam::DataType data_type = (it.value().type() != NodeParam::kNone)
? it.value().type()
: it.key()->data_type();
: corresponding_input->data_type();
switch (data_type) {
case NodeInput::kInt:
@@ -300,7 +303,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
shader->setUniformValue(variable_location, value.toFloat());
break;
case NodeInput::kVec2:
if (it.key()->IsArray()) {
if (corresponding_input && corresponding_input->IsArray()) {
QVector<NodeValue> nv = value.value< QVector<NodeValue> >();
QVector<QVector2D> a(nv.size());
@@ -310,7 +313,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
shader->setUniformValueArray(variable_location, a.constData(), a.size());
int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(it.key()->id()));
int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(it.key()));
if (count_location > -1) {
shader->setUniformValue(count_location, a.size());
}
@@ -355,7 +358,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
shader->setUniformValue(variable_location, textures_to_bind.size());
// If this texture binding is the iterative input, set it here
if (it.key() == job.GetIterativeInput()) {
if (corresponding_input && corresponding_input == job.GetIterativeInput()) {
iterative_input = textures_to_bind.size();
}
@@ -363,7 +366,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
textures_to_bind.append(tex_id);
// Set enable flag if shader wants it
int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(it.key()->id()));
int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(it.key()));
if (enable_param_location > -1) {
shader->setUniformValue(enable_param_location,
tex_id > 0);
@@ -371,7 +374,7 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
if (tex_id > 0) {
// Set texture resolution if shader wants it
int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(it.key()->id()));
int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(it.key()));
if (res_param_location > -1) {
shader->setUniformValue(res_param_location,
static_cast<GLfloat>(texture->texture()->width() * texture->texture()->divider()),
@@ -404,19 +407,6 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
static_cast<GLfloat>(params.width()),
static_cast<GLfloat>(params.height()));
if (node->IsBlock() && static_cast<const Block*>(node)->type() == Block::kTransition) {
const TransitionBlock* transition_node = static_cast<const TransitionBlock*>(node);
// Provides total transition progress from 0.0 (start) - 1.0 (end)
shader->setUniformValue("ove_tprog_all", static_cast<GLfloat>(transition_node->GetTotalProgress(range.in())));
// Provides progress of out section from 1.0 (start) - 0.0 (end)
shader->setUniformValue("ove_tprog_out", static_cast<GLfloat>(transition_node->GetOutProgress(range.in())));
// Provides progress of in section from 0.0 (start) - 1.0 (end)
shader->setUniformValue("ove_tprog_in", static_cast<GLfloat>(transition_node->GetInProgress(range.in())));
}
shader->release();
// Create the output textures
+12 -1
View File
@@ -230,9 +230,20 @@ QVariant RenderWorker::ProcessSamples(const Node *node, const TimeRange &range,
// Update all non-sample and non-footage inputs
NodeValueMap::const_iterator j;
for (j=job.GetValues().constBegin(); j!=job.GetValues().constEnd(); j++) {
value_db.Insert(j.key(), ProcessInput(j.key(), TimeRange(this_sample_time, this_sample_time)));
NodeValueTable value;
NodeInput* corresponding_input = node->GetInputWithID(j.key());
if (corresponding_input) {
value = ProcessInput(corresponding_input, TimeRange(this_sample_time, this_sample_time));
} else {
value.Push(j.value());
}
value_db.Insert(j.key(), value);
}
AddGlobalsToDatabase(value_db, TimeRange(this_sample_time, this_sample_time));
node->ProcessSamples(value_db,
job.samples(),
output_buffer,
+14 -4
View File
@@ -8,13 +8,18 @@
OLIVE_NAMESPACE_ENTER
using NodeValueMap = QHash<NodeInput*, NodeValue>;
using NodeValueMap = QHash<QString, NodeValue>;
class AcceleratedJob {
public:
AcceleratedJob() = default;
NodeValue GetValue(NodeInput* input) const
{
return value_map_.value(input->id());
}
NodeValue GetValue(const QString& input) const
{
return value_map_.value(input);
}
@@ -31,15 +36,20 @@ public:
values[j] = value[subparam].TakeWithMeta(subparam->data_type());
}
value_map_.insert(input, NodeValue(NodeParam::kVec2, QVariant::fromValue(values), input->parentNode()));
InsertValue(input->id(), NodeValue(NodeParam::kVec2, QVariant::fromValue(values), input->parentNode()));
} else {
value_map_.insert(input, value[input].TakeWithMeta(input->data_type()));
InsertValue(input->id(), value[input].TakeWithMeta(input->data_type()));
}
}
void InsertValue(const QString& input, const NodeValue& value)
{
value_map_.insert(input, value);
}
void InsertValue(NodeInput* input, const NodeValue& value)
{
value_map_.insert(input, value);
value_map_.insert(input->id(), value);
}
const NodeValueMap &GetValues() const
+17 -2
View File
@@ -1,9 +1,14 @@
#version 150
#define LINEAR_CURVE 0
#define EXPONENTIAL_CURVE 1
#define LOGARITHMIC_CURVE 2
uniform sampler2D out_block_in;
uniform sampler2D in_block_in;
uniform bool out_block_in_enabled;
uniform bool in_block_in_enabled;
uniform int curve_in;
uniform float ove_tprog_all;
@@ -11,15 +16,25 @@ in vec2 ove_texcoord;
out vec4 fragColor;
float TransformCurve(float linear) {
if (curve_in == EXPONENTIAL_CURVE) {
return linear * linear;
} else if (curve_in == LOGARITHMIC_CURVE) {
return sqrt(linear);
} else {
return linear;
}
}
void main(void) {
vec4 composite = vec4(0.0);
if (out_block_in_enabled) {
composite += texture(out_block_in, ove_texcoord) * (1.0 - ove_tprog_all);
composite += texture(out_block_in, ove_texcoord) * TransformCurve(1.0 - ove_tprog_all);
}
if (in_block_in_enabled) {
composite += texture(in_block_in, ove_texcoord) * ove_tprog_all;
composite += texture(in_block_in, ove_texcoord) * TransformCurve(ove_tprog_all);
}
fragColor = composite;