diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 0180fbfaa..38a6cc7da 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -60,35 +60,18 @@ QString VolumeNode::Description() const NodeValueTable VolumeNode::Value(NodeValueDatabase &value) const { - SampleJob job(samples_input_, value); - job.InsertValue(volume_input_, value); - - NodeValueTable table = value.Merge(); - - if (job.HasSamples()) { - float volume_val = job.GetValue(volume_input_).data().toFloat(); - if (volume_input_->is_static()) { - // Volume never changes so we can make some performance optimizations here - if (!qFuzzyCompare(volume_val, 1.0f)) { - job.samples()->transform_volume(volume_val); - } - - table.Push(NodeParam::kSamples, QVariant::fromValue(job.samples()), this); - } else { - table.Push(NodeParam::kSampleJob, QVariant::fromValue(job), this); - } - } - - return table; + return ValueInternal(value, + kOpMultiply, + kPairSampleNumber, + samples_input_, + value[samples_input_].TakeWithMeta(NodeParam::kSamples), + volume_input_, + value[volume_input_].TakeWithMeta(NodeParam::kFloat)); } void VolumeNode::ProcessSamples(NodeValueDatabase &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const { - float volume_val = values[volume_input_].Get(NodeParam::kFloat).toFloat(); - - for (int i=0;iaudio_params().channel_count();i++) { - output->data()[i][index] = input->data()[i][index] * volume_val; - } + return ProcessSamplesInternal(values, kOpMultiply, samples_input_, volume_input_, input, output, index); } void VolumeNode::Retranslate() diff --git a/app/node/audio/volume/volume.h b/app/node/audio/volume/volume.h index 7547eeffd..e408e3be7 100644 --- a/app/node/audio/volume/volume.h +++ b/app/node/audio/volume/volume.h @@ -21,11 +21,11 @@ #ifndef VOLUMENODE_H #define VOLUMENODE_H -#include "node/node.h" +#include "node/math/math/mathbase.h" OLIVE_NAMESPACE_ENTER -class VolumeNode : public Node +class VolumeNode : public MathNodeBase { public: VolumeNode(); diff --git a/app/node/math/math/CMakeLists.txt b/app/node/math/math/CMakeLists.txt index c0a2653da..cfd3f7ab9 100644 --- a/app/node/math/math/CMakeLists.txt +++ b/app/node/math/math/CMakeLists.txt @@ -18,5 +18,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} node/math/math/math.h node/math/math/math.cpp + node/math/math/mathbase.h + node/math/math/mathbase.cpp PARENT_SCOPE ) diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp index c68879a7d..9330d64d3 100644 --- a/app/node/math/math/math.cpp +++ b/app/node/math/math/math.cpp @@ -20,12 +20,6 @@ #include "math.h" -#include -#include - -#include "common/tohex.h" -#include "render/color.h" - OLIVE_NAMESPACE_ENTER MathNode::MathNode() @@ -91,69 +85,7 @@ void MathNode::Retranslate() ShaderCode MathNode::GetShaderCode(const QString &shader_id) const { - QStringList code_id = shader_id.split('.'); - - Operation op = static_cast(code_id.at(0).toInt()); - Pairing pairing = static_cast(code_id.at(1).toInt()); - NodeParam::DataType type_a = static_cast(code_id.at(2).toInt()); - NodeParam::DataType type_b = static_cast(code_id.at(3).toInt()); - - QString operation, frag, vert; - - if (pairing == kPairTextureMatrix && op == kOpMultiply) { - - // Override the operation for this operation since we multiply texture COORDS by the matrix rather than - NodeParam* tex_in = (type_a == NodeParam::kTexture) ? param_a_in_ : param_b_in_; - - // No-op frag shader (can we return QString() instead?) - operation = QStringLiteral("texture(%1, ove_texcoord)").arg(tex_in->id()); - - // Override the operation for this operation since we multiply texture COORDS by the matrix rather than - NodeParam* mat_in = (type_a == NodeParam::kTexture) ? param_b_in_ : param_a_in_; - - vert = ReadFileAsString(":/shaders/matrix.vert").arg(mat_in->id(), tex_in->id()); - - } else { - switch (op) { - case kOpAdd: - operation = QStringLiteral("%1 + %2"); - break; - case kOpSubtract: - operation = QStringLiteral("%1 - %2"); - break; - case kOpMultiply: - operation = QStringLiteral("%1 * %2"); - break; - case kOpDivide: - operation = QStringLiteral("%1 / %2"); - break; - case kOpPower: - operation = QStringLiteral("pow(%1, %2)"); - break; - } - - operation = operation.arg(GetShaderVariableCall(param_a_in_->id(), type_a), - GetShaderVariableCall(param_b_in_->id(), type_b)); - } - - frag = QStringLiteral("#version 150\n" - "\n" - "uniform %1 %3;\n" - "uniform %2 %4;\n" - "\n" - "in vec2 ove_texcoord;\n" - "\n" - "out vec4 fragColor;\n" - "\n" - "void main(void) {\n" - " fragColor = %5;\n" - "}\n").arg(GetShaderUniformType(type_a), - GetShaderUniformType(type_b), - param_a_in_->id(), - param_b_in_->id(), - operation); - - return ShaderCode(frag, vert); + return GetShaderCodeInternal(shader_id, param_a_in_, param_b_in_); } NodeValueTable MathNode::Value(NodeValueDatabase &value) const @@ -173,524 +105,18 @@ NodeValueTable MathNode::Value(NodeValueDatabase &value) const NodeValue val_b = calc.GetMostLikelyValueB(); value[param_b_in_].Remove(val_b); - NodeValueTable output = value.Merge(); - - switch (calc.GetMostLikelyPairing()) { - - case kPairNumberNumber: - { - if (val_a.type() == NodeParam::kRational && val_b.type() == NodeParam::kRational && GetOperation() != kOpPower) { - // Preserve rationals - output.Push(NodeParam::kRational, - QVariant::fromValue(PerformAddSubMultDiv(val_a.data().value(), val_b.data().value())), - this); - } else { - output.Push(NodeParam::kFloat, - PerformAll(RetrieveNumber(val_a), RetrieveNumber(val_b)), - this); - } - break; - } - - case kPairVecVec: - { - // We convert all vectors to QVector4D just for simplicity and exploit the fact that kVec4 is higher than kVec2 in - // the enum to find the largest data type - PushVector(&output, - qMax(val_a.type(), val_b.type()), - PerformAddSubMultDiv(RetrieveVector(val_a), RetrieveVector(val_b))); - break; - } - - case kPairMatrixVec: - { - QMatrix4x4 matrix = (val_a.type() == NodeParam::kMatrix) ? val_a.data().value() : val_b.data().value(); - QVector4D vec = (val_a.type() == NodeParam::kMatrix) ? RetrieveVector(val_b) : RetrieveVector(val_a); - - // Only valid operation is multiply - PushVector(&output, - qMax(val_a.type(), val_b.type()), - PerformMult(vec, matrix)); - break; - } - - case kPairVecNumber: - { - QVector4D vec = (val_a.type() & NodeParam::kVector) ? RetrieveVector(val_a) : RetrieveVector(val_b); - float number = RetrieveNumber((val_a.type() & NodeParam::kMatrix) ? val_b : val_a); - - // Only multiply and divide are valid operations - PushVector(&output, val_a.type(), PerformMultDiv(vec, number)); - break; - } - - case kPairMatrixMatrix: - { - QMatrix4x4 mat_a = val_a.data().value(); - QMatrix4x4 mat_b = val_b.data().value(); - output.Push(NodeParam::kMatrix, PerformAddSubMult(mat_a, mat_b), this); - break; - } - - case kPairColorColor: - { - Color col_a = val_a.data().value(); - Color col_b = val_b.data().value(); - - // Only add and subtract are valid operations - output.Push(NodeParam::kColor, QVariant::fromValue(PerformAddSub(col_a, col_b)), this); - break; - } - - - case kPairNumberColor: - { - Color col = (val_a.type() == NodeParam::kColor) ? val_a.data().value() : val_b.data().value(); - float num = (val_a.type() == NodeParam::kColor) ? val_b.data().toFloat() : val_a.data().toFloat(); - - // Only multiply and divide are valid operations - output.Push(NodeParam::kColor, QVariant::fromValue(PerformMult(col, num)), this); - break; - } - - case kPairSampleSample: - { - SampleBufferPtr samples_a = val_a.data().value(); - SampleBufferPtr samples_b = val_b.data().value(); - - int max_samples = qMax(samples_a->sample_count(), samples_b->sample_count()); - int min_samples = qMin(samples_a->sample_count(), samples_b->sample_count()); - - SampleBufferPtr mixed_samples = SampleBuffer::CreateAllocated(samples_a->audio_params(), max_samples); - - // Mix samples that are in both buffers - for (int i=0;iaudio_params().channel_count();i++) { - for (int j=0;jdata()[i][j] = PerformAll(samples_a->data()[i][j], samples_b->data()[i][j]); - } - } - - if (max_samples > min_samples) { - // Fill in remainder space with 0s - int remainder = max_samples - min_samples; - - for (int i=0;iaudio_params().channel_count();i++) { - memset(mixed_samples->data()[i] + min_samples * sizeof(float), - 0, - remainder * sizeof(float)); - } - } - - output.Push(NodeParam::kSamples, QVariant::fromValue(mixed_samples), this); - break; - } - - case kPairTextureColor: - case kPairTextureNumber: - case kPairTextureTexture: - case kPairTextureMatrix: - { - ShaderJob job; - job.SetShaderID(QStringLiteral("%1.%2.%3.%4").arg(QString::number(GetOperation()), - QString::number(calc.GetMostLikelyPairing()), - QString::number(val_a.type()), - QString::number(val_b.type()))); - - job.InsertValue(param_a_in_, val_a); - job.InsertValue(param_b_in_, val_b); - - bool operation_is_noop = false; - - const NodeValue& number_val = val_a.type() == NodeParam::kTexture ? val_b : val_a; - - if (calc.GetMostLikelyPairing() == kPairTextureNumber) { - if (NumberIsNoOp(GetOperation(), RetrieveNumber(number_val))) { - operation_is_noop = true; - } - } else if (calc.GetMostLikelyPairing() == kPairTextureMatrix) { - // Only allow matrix multiplication - if (GetOperation() != kOpMultiply - || number_val.data().value().isIdentity()) { - operation_is_noop = true; - } else { - // It's likely an alpha channel will result from this operation - job.SetAlphaChannelRequired(true); - } - } - - if (operation_is_noop) { - // Just push texture as-is - output.Push(val_a.type() == NodeParam::kTexture ? val_a : val_b); - } else { - // Push shader job - output.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); - } - break; - } - - case kPairSampleNumber: - { - // Queue a sample job - const NodeValue& number_val = val_a.type() == NodeParam::kSamples ? val_b : val_a; - NodeInput* number_param = val_a.type() == NodeParam::kSamples ? param_b_in_ : param_a_in_; - - float number = RetrieveNumber(number_val); - - SampleJob job(val_a.type() == NodeParam::kSamples ? val_a : val_b); - job.InsertValue(number_param, NodeValue(NodeParam::kFloat, number, this)); - - if (job.HasSamples()) { - if (number_param->is_static()) { - if (!NumberIsNoOp(GetOperation(), number)) { - for (int i=0;iaudio_params().channel_count();i++) { - for (int j=0;jsample_count();j++) { - job.samples()->data()[i][j] = PerformAll(job.samples()->data()[i][j], number); - } - } - } - - output.Push(NodeParam::kSamples, QVariant::fromValue(job.samples()), this); - } else { - output.Push(NodeParam::kSampleJob, QVariant::fromValue(job), this); - } - } - break; - } - - case kPairNone: - case kPairCount: - break; - } - - return output; + return ValueInternal(value, + GetOperation(), + calc.GetMostLikelyPairing(), + param_a_in_, + val_a, + param_b_in_, + val_b); } void MathNode::ProcessSamples(NodeValueDatabase &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const { - // This function is only used for sample+number pairing - NodeValue number_val = values[param_a_in_].GetWithMeta(NodeParam::kNumber); - - if (number_val.type() == NodeParam::kNone) { - number_val = values[param_b_in_].GetWithMeta(NodeParam::kNumber); - - if (number_val.type() == NodeParam::kNone) { - return; - } - } - - float number_flt = RetrieveNumber(number_val); - - for (int i=0;iaudio_params().channel_count();i++) { - output->data()[i][index] = PerformAll(input->data()[i][index], number_flt); - } -} - -NodeInput *MathNode::param_a_in() const -{ - return param_a_in_; -} - -NodeInput *MathNode::param_b_in() const -{ - return param_b_in_; -} - -MathNode::Operation MathNode::GetOperation() const -{ - return static_cast(method_in_->get_standard_value().toInt()); -} - -void MathNode::SetOperation(MathNode::Operation o) -{ - method_in_->set_standard_value(o); -} - -QString MathNode::GetShaderUniformType(const NodeParam::DataType &type) -{ - switch (type) { - case NodeParam::kTexture: - return QStringLiteral("sampler2D"); - case NodeParam::kColor: - return QStringLiteral("vec4"); - case NodeParam::kMatrix: - return QStringLiteral("mat4"); - default: - return QStringLiteral("float"); - } -} - -QString MathNode::GetShaderVariableCall(const QString &input_id, const NodeParam::DataType &type, const QString& coord_op) -{ - if (type == NodeParam::kTexture) { - return QStringLiteral("texture(%1, ove_texcoord%2)").arg(input_id, coord_op); - } - - return input_id; -} - -QVector4D MathNode::RetrieveVector(const NodeValue &val) -{ - // QVariant doesn't know that QVector*D can convert themselves so we do it here - switch (val.type()) { - case NodeParam::kVec2: - return val.data().value(); - case NodeParam::kVec3: - return val.data().value(); - case NodeParam::kVec4: - default: - return val.data().value(); - } -} - -void MathNode::PushVector(NodeValueTable *output, NodeParam::DataType type, const QVector4D &vec) const -{ - switch (type) { - case NodeParam::kVec2: - output->Push(type, QVector2D(vec), this); - break; - case NodeParam::kVec3: - output->Push(type, QVector3D(vec), this); - break; - case NodeParam::kVec4: - output->Push(type, vec, this); - break; - default: - break; - } -} - -float MathNode::RetrieveNumber(const NodeValue &val) -{ - if (val.type() == NodeParam::kRational) { - return val.data().value().toDouble(); - } else { - return val.data().toFloat(); - } -} - -bool MathNode::NumberIsNoOp(const MathNode::Operation &op, const float &number) -{ - switch (op) { - case kOpAdd: - case kOpSubtract: - if (qIsNull(number)) { - return true; - } - break; - case kOpMultiply: - case kOpDivide: - case kOpPower: - if (qFuzzyCompare(number, 1.0f)) { - return true; - } - break; - } - - return false; -} - -MathNode::PairingCalculator::PairingCalculator(const NodeValueTable &table_a, const NodeValueTable &table_b) -{ - QVector pair_likelihood_a = GetPairLikelihood(table_a); - QVector pair_likelihood_b = GetPairLikelihood(table_b); - - int weight_a = qMax(0, table_b.Count() - table_a.Count()); - int weight_b = qMax(0, table_a.Count() - table_b.Count()); - - QVector likelihoods(kPairCount); - - for (int i=0;i -1) { - if (most_likely_pairing_ == kPairNone - || likelihoods.at(i) > likelihoods.at(most_likely_pairing_)) { - most_likely_pairing_ = static_cast(i); - } - } - } - - if (most_likely_pairing_ != kPairNone) { - most_likely_value_a_ = table_a.at(pair_likelihood_a.at(most_likely_pairing_)); - most_likely_value_b_ = table_b.at(pair_likelihood_b.at(most_likely_pairing_)); - } -} - -QVector MathNode::PairingCalculator::GetPairLikelihood(const NodeValueTable &table) -{ - // FIXME: When we introduce a manual override, placing it here would be the least problematic - - QVector likelihood(kPairCount, -1); - - for (int i=0;i kPairNone && most_likely_pairing_ < kPairCount); -} - -MathNode::Pairing MathNode::PairingCalculator::GetMostLikelyPairing() const -{ - return most_likely_pairing_; -} - -const NodeValue &MathNode::PairingCalculator::GetMostLikelyValueA() const -{ - return most_likely_value_a_; -} - -const NodeValue &MathNode::PairingCalculator::GetMostLikelyValueB() const -{ - return most_likely_value_b_; -} - -template -T MathNode::PerformAll(T a, U b) const -{ - switch (GetOperation()) { - case kOpAdd: - return a + b; - case kOpSubtract: - return a - b; - case kOpMultiply: - return a * b; - case kOpDivide: - return a / b; - case kOpPower: - return qPow(a, b); - } - - return a; -} - -template -T MathNode::PerformMultDiv(T a, U b) const -{ - switch (GetOperation()) { - case kOpMultiply: - return a * b; - case kOpDivide: - return a / b; - case kOpAdd: - case kOpSubtract: - case kOpPower: - break; - } - - return a; -} - -template -T MathNode::PerformAddSub(T a, U b) const -{ - switch (GetOperation()) { - case kOpAdd: - return a + b; - case kOpSubtract: - return a - b; - case kOpMultiply: - case kOpDivide: - case kOpPower: - break; - } - - return a; -} - -template -T MathNode::PerformMult(T a, U b) const -{ - switch (GetOperation()) { - case kOpMultiply: - return a * b; - case kOpAdd: - case kOpSubtract: - case kOpDivide: - case kOpPower: - break; - } - - return a; -} - -template -T MathNode::PerformAddSubMult(T a, U b) const -{ - switch (GetOperation()) { - case kOpAdd: - return a + b; - case kOpSubtract: - return a - b; - case kOpMultiply: - return a * b; - case kOpDivide: - case kOpPower: - break; - } - - return a; -} - -template -T MathNode::PerformAddSubMultDiv(T a, U b) const -{ - switch (GetOperation()) { - case kOpAdd: - return a + b; - case kOpSubtract: - return a - b; - case kOpMultiply: - return a * b; - case kOpDivide: - return a / b; - case kOpPower: - break; - } - - return a; + return ProcessSamplesInternal(values, GetOperation(), param_a_in_, param_b_in_, input, output, index); } OLIVE_NAMESPACE_EXIT diff --git a/app/node/math/math/math.h b/app/node/math/math/math.h index b99442dc2..81e5111b1 100644 --- a/app/node/math/math/math.h +++ b/app/node/math/math/math.h @@ -21,11 +21,11 @@ #ifndef MATHNODE_H #define MATHNODE_H -#include "node/node.h" +#include "mathbase.h" OLIVE_NAMESPACE_ENTER -class MathNode : public Node +class MathNode : public MathNodeBase { public: MathNode(); @@ -40,97 +40,32 @@ public: virtual void Retranslate() override; virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + + Operation GetOperation() const + { + return static_cast(method_in_->get_standard_value().toInt()); + } + + void SetOperation(Operation o) + { + method_in_->set_standard_value(o); + } + + NodeInput* param_a_in() const + { + return param_a_in_; + } + + NodeInput* param_b_in() const + { + return param_b_in_; + } + virtual NodeValueTable Value(NodeValueDatabase &value) const override; virtual void ProcessSamples(NodeValueDatabase &values, const SampleBufferPtr input, SampleBufferPtr output, int index) const override; - NodeInput* param_a_in() const; - NodeInput* param_b_in() const; - - enum Operation { - kOpAdd, - kOpSubtract, - kOpMultiply, - kOpDivide, - kOpPower - }; - - Operation GetOperation() const; - void SetOperation(Operation o); - private: - enum Pairing { - kPairNone = -1, - - kPairNumberNumber, - kPairVecVec, - kPairMatrixMatrix, - kPairColorColor, - kPairTextureTexture, - - kPairVecNumber, - kPairMatrixVec, - kPairNumberColor, - kPairTextureNumber, - kPairTextureColor, - kPairTextureMatrix, - kPairSampleSample, - kPairSampleNumber, - - kPairCount - }; - - class PairingCalculator { - public: - PairingCalculator(const NodeValueTable &table_a, const NodeValueTable &table_b); - - bool FoundMostLikelyPairing() const; - Pairing GetMostLikelyPairing() const; - - const NodeValue& GetMostLikelyValueA() const; - const NodeValue& GetMostLikelyValueB() const; - - private: - static QVector GetPairLikelihood(const NodeValueTable& table); - - Pairing most_likely_pairing_; - - NodeValue most_likely_value_a_; - - NodeValue most_likely_value_b_; - - }; - - template - T PerformAll(T a, U b) const; - - template - T PerformMultDiv(T a, U b) const; - - template - T PerformAddSub(T a, U b) const; - - template - T PerformMult(T a, U b) const; - - template - T PerformAddSubMult(T a, U b) const; - - template - T PerformAddSubMultDiv(T a, U b) const; - - static QString GetShaderUniformType(const NodeParam::DataType& type); - - static QString GetShaderVariableCall(const QString& input_id, const NodeParam::DataType& type, const QString &coord_op = QString()); - - static QVector4D RetrieveVector(const NodeValue& val); - - static float RetrieveNumber(const NodeValue& val); - - static bool NumberIsNoOp(const Operation& op, const float& number); - - void PushVector(NodeValueTable* output, NodeParam::DataType type, const QVector4D& vec) const; - NodeInput* method_in_; NodeInput* param_a_in_; diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp new file mode 100644 index 000000000..50ae6c2d8 --- /dev/null +++ b/app/node/math/math/mathbase.cpp @@ -0,0 +1,598 @@ +/*** + + 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 . + +***/ + +#include "math.h" + +#include +#include + +#include "common/tohex.h" +#include "render/color.h" + +OLIVE_NAMESPACE_ENTER + +ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, NodeInput *param_a_in, olive::NodeInput *param_b_in) const +{ + QStringList code_id = shader_id.split('.'); + + Operation op = static_cast(code_id.at(0).toInt()); + Pairing pairing = static_cast(code_id.at(1).toInt()); + NodeParam::DataType type_a = static_cast(code_id.at(2).toInt()); + NodeParam::DataType type_b = static_cast(code_id.at(3).toInt()); + + QString operation, frag, vert; + + if (pairing == kPairTextureMatrix && op == kOpMultiply) { + + // Override the operation for this operation since we multiply texture COORDS by the matrix rather than + NodeParam* tex_in = (type_a == NodeParam::kTexture) ? param_a_in : param_b_in; + NodeParam* mat_in = (type_a == NodeParam::kTexture) ? param_b_in : param_a_in; + + // No-op frag shader (can we return QString() instead?) + operation = QStringLiteral("texture(%1, ove_texcoord)").arg(tex_in->id()); + + vert = ReadFileAsString(":/shaders/matrix.vert").arg(mat_in->id(), tex_in->id()); + + } else { + switch (op) { + case kOpAdd: + operation = QStringLiteral("%1 + %2"); + break; + case kOpSubtract: + operation = QStringLiteral("%1 - %2"); + break; + case kOpMultiply: + operation = QStringLiteral("%1 * %2"); + break; + case kOpDivide: + operation = QStringLiteral("%1 / %2"); + break; + case kOpPower: + operation = QStringLiteral("pow(%1, %2)"); + break; + } + + operation = operation.arg(GetShaderVariableCall(param_a_in->id(), type_a), + GetShaderVariableCall(param_b_in->id(), type_b)); + } + + frag = QStringLiteral("#version 150\n" + "\n" + "uniform %1 %3;\n" + "uniform %2 %4;\n" + "\n" + "in vec2 ove_texcoord;\n" + "\n" + "out vec4 fragColor;\n" + "\n" + "void main(void) {\n" + " fragColor = %5;\n" + "}\n").arg(GetShaderUniformType(type_a), + GetShaderUniformType(type_b), + param_a_in->id(), + param_b_in->id(), + operation); + + return ShaderCode(frag, vert); +} + +QString MathNodeBase::GetShaderUniformType(const NodeParam::DataType &type) +{ + switch (type) { + case NodeParam::kTexture: + return QStringLiteral("sampler2D"); + case NodeParam::kColor: + return QStringLiteral("vec4"); + case NodeParam::kMatrix: + return QStringLiteral("mat4"); + default: + return QStringLiteral("float"); + } +} + +QString MathNodeBase::GetShaderVariableCall(const QString &input_id, const NodeParam::DataType &type, const QString& coord_op) +{ + if (type == NodeParam::kTexture) { + return QStringLiteral("texture(%1, ove_texcoord%2)").arg(input_id, coord_op); + } + + return input_id; +} + +QVector4D MathNodeBase::RetrieveVector(const NodeValue &val) +{ + // QVariant doesn't know that QVector*D can convert themselves so we do it here + switch (val.type()) { + case NodeParam::kVec2: + return val.data().value(); + case NodeParam::kVec3: + return val.data().value(); + case NodeParam::kVec4: + default: + return val.data().value(); + } +} + +void MathNodeBase::PushVector(NodeValueTable *output, NodeParam::DataType type, const QVector4D &vec) const +{ + switch (type) { + case NodeParam::kVec2: + output->Push(type, QVector2D(vec), this); + break; + case NodeParam::kVec3: + output->Push(type, QVector3D(vec), this); + break; + case NodeParam::kVec4: + output->Push(type, vec, this); + break; + default: + break; + } +} + +NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation operation, Pairing pairing, NodeInput *param_a_in, const NodeValue& val_a, NodeInput *param_b_in, const NodeValue& val_b) const +{ + NodeValueTable output = value.Merge(); + + switch (pairing) { + + case kPairNumberNumber: + { + if (val_a.type() == NodeParam::kRational && val_b.type() == NodeParam::kRational && operation != kOpPower) { + // Preserve rationals + output.Push(NodeParam::kRational, + QVariant::fromValue(PerformAddSubMultDiv(operation, val_a.data().value(), val_b.data().value())), + this); + } else { + output.Push(NodeParam::kFloat, + PerformAll(operation, RetrieveNumber(val_a), RetrieveNumber(val_b)), + this); + } + break; + } + + case kPairVecVec: + { + // We convert all vectors to QVector4D just for simplicity and exploit the fact that kVec4 is higher than kVec2 in + // the enum to find the largest data type + PushVector(&output, + qMax(val_a.type(), val_b.type()), + PerformAddSubMultDiv(operation, RetrieveVector(val_a), RetrieveVector(val_b))); + break; + } + + case kPairMatrixVec: + { + QMatrix4x4 matrix = (val_a.type() == NodeParam::kMatrix) ? val_a.data().value() : val_b.data().value(); + QVector4D vec = (val_a.type() == NodeParam::kMatrix) ? RetrieveVector(val_b) : RetrieveVector(val_a); + + // Only valid operation is multiply + PushVector(&output, + qMax(val_a.type(), val_b.type()), + PerformMult(operation, vec, matrix)); + break; + } + + case kPairVecNumber: + { + QVector4D vec = (val_a.type() & NodeParam::kVector) ? RetrieveVector(val_a) : RetrieveVector(val_b); + float number = RetrieveNumber((val_a.type() & NodeParam::kMatrix) ? val_b : val_a); + + // Only multiply and divide are valid operations + PushVector(&output, val_a.type(), PerformMultDiv(operation, vec, number)); + break; + } + + case kPairMatrixMatrix: + { + QMatrix4x4 mat_a = val_a.data().value(); + QMatrix4x4 mat_b = val_b.data().value(); + output.Push(NodeParam::kMatrix, PerformAddSubMult(operation, mat_a, mat_b), this); + break; + } + + case kPairColorColor: + { + Color col_a = val_a.data().value(); + Color col_b = val_b.data().value(); + + // Only add and subtract are valid operations + output.Push(NodeParam::kColor, QVariant::fromValue(PerformAddSub(operation, col_a, col_b)), this); + break; + } + + + case kPairNumberColor: + { + Color col = (val_a.type() == NodeParam::kColor) ? val_a.data().value() : val_b.data().value(); + float num = (val_a.type() == NodeParam::kColor) ? val_b.data().toFloat() : val_a.data().toFloat(); + + // Only multiply and divide are valid operations + output.Push(NodeParam::kColor, QVariant::fromValue(PerformMult(operation, col, num)), this); + break; + } + + case kPairSampleSample: + { + SampleBufferPtr samples_a = val_a.data().value(); + SampleBufferPtr samples_b = val_b.data().value(); + + int max_samples = qMax(samples_a->sample_count(), samples_b->sample_count()); + int min_samples = qMin(samples_a->sample_count(), samples_b->sample_count()); + + SampleBufferPtr mixed_samples = SampleBuffer::CreateAllocated(samples_a->audio_params(), max_samples); + + // Mix samples that are in both buffers + for (int i=0;iaudio_params().channel_count();i++) { + for (int j=0;jdata()[i][j] = PerformAll(operation, samples_a->data()[i][j], samples_b->data()[i][j]); + } + } + + if (max_samples > min_samples) { + // Fill in remainder space with 0s + int remainder = max_samples - min_samples; + + for (int i=0;iaudio_params().channel_count();i++) { + memset(mixed_samples->data()[i] + min_samples * sizeof(float), + 0, + remainder * sizeof(float)); + } + } + + output.Push(NodeParam::kSamples, QVariant::fromValue(mixed_samples), this); + break; + } + + case kPairTextureColor: + case kPairTextureNumber: + case kPairTextureTexture: + case kPairTextureMatrix: + { + ShaderJob job; + job.SetShaderID(QStringLiteral("%1.%2.%3.%4").arg(QString::number(operation), + QString::number(pairing), + QString::number(val_a.type()), + QString::number(val_b.type()))); + + job.InsertValue(param_a_in, val_a); + job.InsertValue(param_b_in, val_b); + + bool operation_is_noop = false; + + const NodeValue& number_val = val_a.type() == NodeParam::kTexture ? val_b : val_a; + + if (pairing == kPairTextureNumber) { + if (NumberIsNoOp(operation, RetrieveNumber(number_val))) { + operation_is_noop = true; + } + } else if (pairing == kPairTextureMatrix) { + // Only allow matrix multiplication + if (operation != kOpMultiply + || number_val.data().value().isIdentity()) { + operation_is_noop = true; + } else { + // It's likely an alpha channel will result from this operation + job.SetAlphaChannelRequired(true); + } + } + + if (operation_is_noop) { + // Just push texture as-is + output.Push(val_a.type() == NodeParam::kTexture ? val_a : val_b); + } else { + // Push shader job + output.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); + } + break; + } + + case kPairSampleNumber: + { + // Queue a sample job + const NodeValue& number_val = val_a.type() == NodeParam::kSamples ? val_b : val_a; + NodeInput* number_param = val_a.type() == NodeParam::kSamples ? param_b_in : param_a_in; + + float number = RetrieveNumber(number_val); + + SampleJob job(val_a.type() == NodeParam::kSamples ? val_a : val_b); + job.InsertValue(number_param, NodeValue(NodeParam::kFloat, number, this)); + + if (job.HasSamples()) { + if (number_param->is_static()) { + if (!NumberIsNoOp(operation, number)) { + for (int i=0;iaudio_params().channel_count();i++) { + for (int j=0;jsample_count();j++) { + job.samples()->data()[i][j] = PerformAll(operation, job.samples()->data()[i][j], number); + } + } + } + + output.Push(NodeParam::kSamples, QVariant::fromValue(job.samples()), this); + } else { + output.Push(NodeParam::kSampleJob, QVariant::fromValue(job), this); + } + } + break; + } + + case kPairNone: + case kPairCount: + break; + } + + return output; +} + +void MathNodeBase::ProcessSamplesInternal(NodeValueDatabase &values, MathNodeBase::Operation operation, NodeInput *param_a_in, NodeInput *param_b_in, const SampleBufferPtr input, SampleBufferPtr output, int index) const +{ + // This function is only used for sample+number pairing + NodeValue number_val = values[param_a_in].GetWithMeta(NodeParam::kNumber); + + if (number_val.type() == NodeParam::kNone) { + number_val = values[param_b_in].GetWithMeta(NodeParam::kNumber); + + if (number_val.type() == NodeParam::kNone) { + return; + } + } + + float number_flt = RetrieveNumber(number_val); + + for (int i=0;iaudio_params().channel_count();i++) { + output->data()[i][index] = PerformAll(operation, input->data()[i][index], number_flt); + } +} + +float MathNodeBase::RetrieveNumber(const NodeValue &val) +{ + if (val.type() == NodeParam::kRational) { + return val.data().value().toDouble(); + } else { + return val.data().toFloat(); + } +} + +bool MathNodeBase::NumberIsNoOp(const MathNodeBase::Operation &op, const float &number) +{ + switch (op) { + case kOpAdd: + case kOpSubtract: + if (qIsNull(number)) { + return true; + } + break; + case kOpMultiply: + case kOpDivide: + case kOpPower: + if (qFuzzyCompare(number, 1.0f)) { + return true; + } + break; + } + + return false; +} + +MathNodeBase::PairingCalculator::PairingCalculator(const NodeValueTable &table_a, const NodeValueTable &table_b) +{ + QVector pair_likelihood_a = GetPairLikelihood(table_a); + QVector pair_likelihood_b = GetPairLikelihood(table_b); + + int weight_a = qMax(0, table_b.Count() - table_a.Count()); + int weight_b = qMax(0, table_a.Count() - table_b.Count()); + + QVector likelihoods(kPairCount); + + for (int i=0;i -1) { + if (most_likely_pairing_ == kPairNone + || likelihoods.at(i) > likelihoods.at(most_likely_pairing_)) { + most_likely_pairing_ = static_cast(i); + } + } + } + + if (most_likely_pairing_ != kPairNone) { + most_likely_value_a_ = table_a.at(pair_likelihood_a.at(most_likely_pairing_)); + most_likely_value_b_ = table_b.at(pair_likelihood_b.at(most_likely_pairing_)); + } +} + +QVector MathNodeBase::PairingCalculator::GetPairLikelihood(const NodeValueTable &table) +{ + // FIXME: When we introduce a manual override, placing it here would be the least problematic + + QVector likelihood(kPairCount, -1); + + for (int i=0;i kPairNone && most_likely_pairing_ < kPairCount); +} + +MathNodeBase::Pairing MathNodeBase::PairingCalculator::GetMostLikelyPairing() const +{ + return most_likely_pairing_; +} + +const NodeValue &MathNodeBase::PairingCalculator::GetMostLikelyValueA() const +{ + return most_likely_value_a_; +} + +const NodeValue &MathNodeBase::PairingCalculator::GetMostLikelyValueB() const +{ + return most_likely_value_b_; +} + +template +T MathNodeBase::PerformAll(Operation operation, T a, U b) +{ + switch (operation) { + case kOpAdd: + return a + b; + case kOpSubtract: + return a - b; + case kOpMultiply: + return a * b; + case kOpDivide: + return a / b; + case kOpPower: + return qPow(a, b); + } + + return a; +} + +template +T MathNodeBase::PerformMultDiv(Operation operation, T a, U b) +{ + switch (operation) { + case kOpMultiply: + return a * b; + case kOpDivide: + return a / b; + case kOpAdd: + case kOpSubtract: + case kOpPower: + break; + } + + return a; +} + +template +T MathNodeBase::PerformAddSub(Operation operation, T a, U b) +{ + switch (operation) { + case kOpAdd: + return a + b; + case kOpSubtract: + return a - b; + case kOpMultiply: + case kOpDivide: + case kOpPower: + break; + } + + return a; +} + +template +T MathNodeBase::PerformMult(Operation operation, T a, U b) +{ + switch (operation) { + case kOpMultiply: + return a * b; + case kOpAdd: + case kOpSubtract: + case kOpDivide: + case kOpPower: + break; + } + + return a; +} + +template +T MathNodeBase::PerformAddSubMult(Operation operation, T a, U b) +{ + switch (operation) { + case kOpAdd: + return a + b; + case kOpSubtract: + return a - b; + case kOpMultiply: + return a * b; + case kOpDivide: + case kOpPower: + break; + } + + return a; +} + +template +T MathNodeBase::PerformAddSubMultDiv(Operation operation, T a, U b) +{ + switch (operation) { + case kOpAdd: + return a + b; + case kOpSubtract: + return a - b; + case kOpMultiply: + return a * b; + case kOpDivide: + return a / b; + case kOpPower: + break; + } + + return a; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/node/math/math/mathbase.h b/app/node/math/math/mathbase.h new file mode 100644 index 000000000..cdea260bf --- /dev/null +++ b/app/node/math/math/mathbase.h @@ -0,0 +1,124 @@ +/*** + + 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 . + +***/ + +#ifndef MATHNODEBASE_H +#define MATHNODEBASE_H + +#include "node/node.h" + +OLIVE_NAMESPACE_ENTER + +class MathNodeBase : public Node +{ +public: + MathNodeBase() = default; + + enum Operation { + kOpAdd, + kOpSubtract, + kOpMultiply, + kOpDivide, + kOpPower + }; + +protected: + enum Pairing { + kPairNone = -1, + + kPairNumberNumber, + kPairVecVec, + kPairMatrixMatrix, + kPairColorColor, + kPairTextureTexture, + + kPairVecNumber, + kPairMatrixVec, + kPairNumberColor, + kPairTextureNumber, + kPairTextureColor, + kPairTextureMatrix, + kPairSampleSample, + kPairSampleNumber, + + kPairCount + }; + + class PairingCalculator { + public: + PairingCalculator(const NodeValueTable &table_a, const NodeValueTable &table_b); + + bool FoundMostLikelyPairing() const; + Pairing GetMostLikelyPairing() const; + + const NodeValue& GetMostLikelyValueA() const; + const NodeValue& GetMostLikelyValueB() const; + + private: + static QVector GetPairLikelihood(const NodeValueTable& table); + + Pairing most_likely_pairing_; + + NodeValue most_likely_value_a_; + + NodeValue most_likely_value_b_; + + }; + + template + static T PerformAll(Operation operation, T a, U b); + + template + static T PerformMultDiv(Operation operation, T a, U b); + + template + static T PerformAddSub(Operation operation, T a, U b); + + template + static T PerformMult(Operation operation, T a, U b); + + template + static T PerformAddSubMult(Operation operation, T a, U b); + + template + static T PerformAddSubMultDiv(Operation operation, T a, U b); + + static QString GetShaderUniformType(const NodeParam::DataType& type); + + static QString GetShaderVariableCall(const QString& input_id, const NodeParam::DataType& type, const QString &coord_op = QString()); + + static QVector4D RetrieveVector(const NodeValue& val); + + static float RetrieveNumber(const NodeValue& val); + + static bool NumberIsNoOp(const Operation& op, const float& number); + + ShaderCode GetShaderCodeInternal(const QString &shader_id, NodeInput* param_a_in, NodeInput* param_b_in) const; + + void PushVector(NodeValueTable* output, NodeParam::DataType type, const QVector4D& vec) const; + + NodeValueTable ValueInternal(NodeValueDatabase &value, Operation operation, Pairing pairing, NodeInput* param_a_in, const NodeValue &val_a, NodeInput* param_b_in, const NodeValue& val_b) const; + + void ProcessSamplesInternal(NodeValueDatabase &values, Operation operation, NodeInput* param_a_in, NodeInput* param_b_in, const SampleBufferPtr input, SampleBufferPtr output, int index) const; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // MATHNODEBASE_H