diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp index c6d471030..2dfe7ace4 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp @@ -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; isample_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; jaudio_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 diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.h b/app/node/block/transition/crossdissolve/crossdissolvetransition.h index ccd8877d3..965267e83 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.h +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.h @@ -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 diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index aa8a77c0a..abf79edc2 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -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(&all_prog), sizeof(double)); hash.addData(reinterpret_cast(&in_prog), sizeof(double)); hash.addData(reinterpret_cast(&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 to_samples = value[in_block_input()].Take(NodeParam::kBuffer).value(); + + 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(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 diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index 50d6ac204..34a6f074d 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -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_; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index d613060c8..5ba40748f 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -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; diff --git a/app/node/traverser.h b/app/node/traverser.h index e9e1523fc..e9a377ae4 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -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); diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index 32c24f92d..fff23b55d 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -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 nv = value.value< QVector >(); QVector 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(texture->texture()->width() * texture->texture()->divider()), @@ -404,19 +407,6 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, static_cast(params.width()), static_cast(params.height())); - if (node->IsBlock() && static_cast(node)->type() == Block::kTransition) { - const TransitionBlock* transition_node = static_cast(node); - - // Provides total transition progress from 0.0 (start) - 1.0 (end) - shader->setUniformValue("ove_tprog_all", static_cast(transition_node->GetTotalProgress(range.in()))); - - // Provides progress of out section from 1.0 (start) - 0.0 (end) - shader->setUniformValue("ove_tprog_out", static_cast(transition_node->GetOutProgress(range.in()))); - - // Provides progress of in section from 0.0 (start) - 1.0 (end) - shader->setUniformValue("ove_tprog_in", static_cast(transition_node->GetInProgress(range.in()))); - } - shader->release(); // Create the output textures diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 37de178b4..31194a105 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -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, diff --git a/app/render/shaderinfo.h b/app/render/shaderinfo.h index 9db03d9e0..1fa685e2b 100644 --- a/app/render/shaderinfo.h +++ b/app/render/shaderinfo.h @@ -8,13 +8,18 @@ OLIVE_NAMESPACE_ENTER -using NodeValueMap = QHash; +using NodeValueMap = QHash; 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 diff --git a/app/shaders/crossdissolve.frag b/app/shaders/crossdissolve.frag index 2af4b17b4..e9461244c 100644 --- a/app/shaders/crossdissolve.frag +++ b/app/shaders/crossdissolve.frag @@ -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;