build: split the engine into liboakengine.so; worker drops the UI entirely
Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).
- oak-render-worker now links liboakengine instead of the whole
libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
../app but backends now live in engine/; a stale pre-split liboakgl
in the build tree got dlopened instead, re-initialized and later
destroyed the interposed engine statics (full-suite segfault at
DialogSequenceParameterTab, found via gdb watchpoint)
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 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/>.
|
||||
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,126 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "math.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const QString MathNode::k_method_in = QStringLiteral("method_in");
|
||||
const QString MathNode::k_param_a_in = QStringLiteral("param_a_in");
|
||||
const QString MathNode::k_param_b_in = QStringLiteral("param_b_in");
|
||||
const QString MathNode::k_param_c_in = QStringLiteral("param_c_in");
|
||||
|
||||
#define super MathNodeBase
|
||||
|
||||
MathNode::MathNode()
|
||||
{
|
||||
add_input(k_method_in, NodeValue::k_combo,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_param_a_in, NodeValue::k_float, 0.0);
|
||||
set_input_property(k_param_a_in, QStringLiteral("decimalplaces"), 8);
|
||||
set_input_property(k_param_a_in, QStringLiteral("autotrim"), true);
|
||||
|
||||
add_input(k_param_b_in, NodeValue::k_float, 0.0);
|
||||
set_input_property(k_param_b_in, QStringLiteral("decimalplaces"), 8);
|
||||
set_input_property(k_param_b_in, QStringLiteral("autotrim"), true);
|
||||
}
|
||||
|
||||
QString MathNode::name() const
|
||||
{
|
||||
// Default to naming after the operation
|
||||
if (parent()) {
|
||||
QString op_name = get_operation_name(get_operation());
|
||||
if (!op_name.isEmpty()) {
|
||||
return op_name;
|
||||
}
|
||||
}
|
||||
|
||||
return tr("Math");
|
||||
}
|
||||
|
||||
QString MathNode::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.math");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> MathNode::category() const
|
||||
{
|
||||
return { k_category_math };
|
||||
}
|
||||
|
||||
QString MathNode::description() const
|
||||
{
|
||||
return tr("Perform a mathematical operation between two values.");
|
||||
}
|
||||
|
||||
void MathNode::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_method_in, tr("Method"));
|
||||
set_input_name(k_param_a_in, tr("Value"));
|
||||
set_input_name(k_param_b_in, tr("Value"));
|
||||
|
||||
QStringList operations = { get_operation_name(k_op_add),
|
||||
get_operation_name(k_op_subtract),
|
||||
get_operation_name(k_op_multiply),
|
||||
get_operation_name(k_op_divide),
|
||||
get_operation_name(k_op_power) };
|
||||
|
||||
set_combo_box_strings(k_method_in, operations);
|
||||
}
|
||||
|
||||
ShaderCode MathNode::get_shader_code(const ShaderRequest &request) const
|
||||
{
|
||||
return get_shader_code_internal(request.id, k_param_a_in, k_param_b_in);
|
||||
}
|
||||
|
||||
void MathNode::value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
// Auto-detect what values to operate with
|
||||
// FIXME: Very inefficient
|
||||
NodeValueTable at, bt;
|
||||
at.push(value[k_param_a_in]);
|
||||
bt.push(value[k_param_b_in]);
|
||||
PairingCalculator calc(at, bt);
|
||||
|
||||
// Do nothing if no pairing was found
|
||||
if (!calc.found_most_likely_pairing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return value_internal(get_operation(), calc.get_most_likely_pairing(), k_param_a_in,
|
||||
calc.get_most_likely_value_a(), k_param_b_in,
|
||||
calc.get_most_likely_value_b(), globals, table);
|
||||
}
|
||||
|
||||
void MathNode::process_samples(const NodeValueRow &values,
|
||||
const SampleBuffer &input, SampleBuffer &output,
|
||||
int index) const
|
||||
{
|
||||
return process_samples_internal(values, get_operation(), k_param_a_in, k_param_b_in,
|
||||
input, output, index);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_MATHNODE_H
|
||||
#define OAK_MATHNODE_H
|
||||
|
||||
#include "mathbase.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class MathNode : public MathNodeBase {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MathNode();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(MathNode)
|
||||
|
||||
virtual QString name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> category() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
virtual ShaderCode
|
||||
get_shader_code(const ShaderRequest &request) const override;
|
||||
|
||||
Operation get_operation() const
|
||||
{
|
||||
return static_cast<Operation>(get_standard_value(k_method_in).toInt());
|
||||
}
|
||||
|
||||
void set_operation(Operation o)
|
||||
{
|
||||
set_standard_value(k_method_in, o);
|
||||
}
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
virtual void process_samples(const NodeValueRow &values,
|
||||
const SampleBuffer &input, SampleBuffer &output,
|
||||
int index) const override;
|
||||
|
||||
static const QString k_method_in;
|
||||
static const QString k_param_a_in;
|
||||
static const QString k_param_b_in;
|
||||
static const QString k_param_c_in;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_MATHNODE_H
|
||||
@@ -0,0 +1,796 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "math.h"
|
||||
|
||||
#include <QMatrix4x4>
|
||||
#include <QVector2D>
|
||||
|
||||
#include "common/tohex.h"
|
||||
#include "node/distort/transform/transformdistortnode.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ShaderCode MathNodeBase::get_shader_code_internal(const QString &shader_id,
|
||||
const QString ¶m_a_in,
|
||||
const QString ¶m_b_in) const
|
||||
{
|
||||
QStringList code_id = shader_id.split('.');
|
||||
|
||||
Operation op = static_cast<Operation>(code_id.at(0).toInt());
|
||||
Pairing pairing = static_cast<Pairing>(code_id.at(1).toInt());
|
||||
NodeValue::Type type_a =
|
||||
static_cast<NodeValue::Type>(code_id.at(2).toInt());
|
||||
NodeValue::Type type_b =
|
||||
static_cast<NodeValue::Type>(code_id.at(3).toInt());
|
||||
|
||||
QString operation, frag, vert;
|
||||
|
||||
if (pairing == k_pair_texture_matrix && op == k_op_multiply) {
|
||||
// Override the operation for this operation since we multiply texture COORDS by the matrix rather than
|
||||
const QString &tex_in = (type_a == NodeValue::k_texture) ? param_a_in :
|
||||
param_b_in;
|
||||
const QString &mat_in = (type_a == NodeValue::k_texture) ? param_b_in :
|
||||
param_a_in;
|
||||
|
||||
// No-op frag shader (can we return QString() instead?)
|
||||
operation = QStringLiteral("texture(%1, ove_texcoord)").arg(tex_in);
|
||||
|
||||
vert = QStringLiteral("uniform mat4 %1;\n"
|
||||
"\n"
|
||||
"in vec4 a_position;\n"
|
||||
"in vec2 a_texcoord;\n"
|
||||
"\n"
|
||||
"out vec2 ove_texcoord;\n"
|
||||
"\n"
|
||||
"void main() {\n"
|
||||
" gl_Position = %1 * a_position;\n"
|
||||
" ove_texcoord = a_texcoord;\n"
|
||||
"}\n")
|
||||
.arg(mat_in);
|
||||
|
||||
} else {
|
||||
switch (op) {
|
||||
case k_op_add:
|
||||
operation = QStringLiteral("%1 + %2");
|
||||
break;
|
||||
case k_op_subtract:
|
||||
operation = QStringLiteral("%1 - %2");
|
||||
break;
|
||||
case k_op_multiply:
|
||||
operation = QStringLiteral("%1 * %2");
|
||||
break;
|
||||
case k_op_divide:
|
||||
operation = QStringLiteral("%1 / %2");
|
||||
break;
|
||||
case k_op_power:
|
||||
if (pairing == k_pair_texture_number) {
|
||||
// The "number" in this operation has to be declared a vec4
|
||||
if (NodeValue::type_is_numeric(type_a)) {
|
||||
operation = QStringLiteral("pow(%2, vec4(%1))");
|
||||
} else {
|
||||
operation = QStringLiteral("pow(%1, vec4(%2))");
|
||||
}
|
||||
} else {
|
||||
operation = QStringLiteral("pow(%1, %2)");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
operation = operation.arg(get_shader_variable_call(param_a_in, type_a),
|
||||
get_shader_variable_call(param_b_in, type_b));
|
||||
}
|
||||
|
||||
frag =
|
||||
QStringLiteral(
|
||||
"uniform %1 %3;\n"
|
||||
"uniform %2 %4;\n"
|
||||
"\n"
|
||||
"in vec2 ove_texcoord;\n"
|
||||
"out vec4 frag_color;\n"
|
||||
"\n"
|
||||
"void main(void) {\n"
|
||||
" vec4 c = %5;\n"
|
||||
" c.a = clamp(c.a, 0.0, 1.0);\n" // Ensure alpha is between 0.0 and 1.0
|
||||
" frag_color = c;\n"
|
||||
"}\n")
|
||||
.arg(get_shader_uniform_type(type_a), get_shader_uniform_type(type_b),
|
||||
param_a_in, param_b_in, operation);
|
||||
|
||||
return ShaderCode(frag, vert);
|
||||
}
|
||||
|
||||
QString MathNodeBase::get_shader_uniform_type(const olive::NodeValue::Type &type)
|
||||
{
|
||||
switch (type) {
|
||||
case NodeValue::k_texture:
|
||||
return QStringLiteral("sampler2D");
|
||||
case NodeValue::k_color:
|
||||
return QStringLiteral("vec4");
|
||||
case NodeValue::k_matrix:
|
||||
return QStringLiteral("mat4");
|
||||
default:
|
||||
return QStringLiteral("float");
|
||||
}
|
||||
}
|
||||
|
||||
QString MathNodeBase::get_shader_variable_call(const QString &input_id,
|
||||
const NodeValue::Type &type,
|
||||
const QString &coord_op)
|
||||
{
|
||||
if (type == NodeValue::k_texture) {
|
||||
return QStringLiteral("texture(%1, ove_texcoord%2)")
|
||||
.arg(input_id, coord_op);
|
||||
}
|
||||
|
||||
return input_id;
|
||||
}
|
||||
|
||||
QVector4D MathNodeBase::retrieve_vector(const NodeValue &val)
|
||||
{
|
||||
// QVariant doesn't know that QVector*D can convert themselves so we do it here
|
||||
switch (val.type()) {
|
||||
case NodeValue::k_vec2:
|
||||
return QVector4D(val.to_vec2());
|
||||
case NodeValue::k_vec3:
|
||||
return QVector4D(val.to_vec3());
|
||||
case NodeValue::k_vec4:
|
||||
default:
|
||||
return val.to_vec4();
|
||||
}
|
||||
}
|
||||
|
||||
void MathNodeBase::push_vector(NodeValueTable *output,
|
||||
olive::NodeValue::Type type,
|
||||
const QVector4D &vec) const
|
||||
{
|
||||
switch (type) {
|
||||
case NodeValue::k_vec2:
|
||||
output->push(type, QVector2D(vec), this);
|
||||
break;
|
||||
case NodeValue::k_vec3:
|
||||
output->push(type, QVector3D(vec), this);
|
||||
break;
|
||||
case NodeValue::k_vec4:
|
||||
output->push(type, vec, this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString MathNodeBase::get_operation_name(Operation o)
|
||||
{
|
||||
switch (o) {
|
||||
case k_op_add:
|
||||
return tr("Add");
|
||||
case k_op_subtract:
|
||||
return tr("Subtract");
|
||||
case k_op_multiply:
|
||||
return tr("Multiply");
|
||||
case k_op_divide:
|
||||
return tr("Divide");
|
||||
case k_op_power:
|
||||
return tr("Power");
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void MathNodeBase::perform_all_on_float_buffer(Operation operation, float *a,
|
||||
float b, int start, int end)
|
||||
{
|
||||
for (int j = start; j < end; j++) {
|
||||
a[j] = perform_all(operation, a[j], b);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
void MathNodeBase::perform_all_on_float_buffer_sse(Operation operation, float *a,
|
||||
float b, int start, int end)
|
||||
{
|
||||
int end_divisible_4 = (end / 4) * 4;
|
||||
|
||||
// Load number to multiply by into buffer
|
||||
__m128 mult = _mm_load1_ps(&b);
|
||||
|
||||
switch (operation) {
|
||||
case k_op_add:
|
||||
// Loop all values
|
||||
for (int j = 0; j < end_divisible_4; j += 4) {
|
||||
_mm_storeu_ps(a + start + j,
|
||||
_mm_add_ps(_mm_loadu_ps(a + start + j), mult));
|
||||
}
|
||||
break;
|
||||
case k_op_subtract:
|
||||
for (int j = 0; j < end_divisible_4; j += 4) {
|
||||
_mm_storeu_ps(a + start + j,
|
||||
_mm_sub_ps(_mm_loadu_ps(a + start + j), mult));
|
||||
}
|
||||
break;
|
||||
case k_op_multiply:
|
||||
for (int j = 0; j < end_divisible_4; j += 4) {
|
||||
_mm_storeu_ps(a + start + j,
|
||||
_mm_mul_ps(_mm_loadu_ps(a + start + j), mult));
|
||||
}
|
||||
break;
|
||||
case k_op_divide:
|
||||
for (int j = 0; j < end_divisible_4; j += 4) {
|
||||
_mm_storeu_ps(a + start + j,
|
||||
_mm_div_ps(_mm_loadu_ps(a + start + j), mult));
|
||||
}
|
||||
break;
|
||||
case k_op_power:
|
||||
// Fallback for operations we can't support here
|
||||
end_divisible_4 = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle last 1-3 bytes if necessary, or all bytes if we couldn't
|
||||
// support this op on SSE
|
||||
perform_all_on_float_buffer(operation, a, b, end_divisible_4, end);
|
||||
}
|
||||
#endif
|
||||
|
||||
void MathNodeBase::value_internal(
|
||||
Operation operation, Pairing pairing, const QString ¶m_a_in,
|
||||
const NodeValue &val_a, const QString ¶m_b_in, const NodeValue &val_b,
|
||||
const NodeGlobals &globals, NodeValueTable *output) const
|
||||
{
|
||||
switch (pairing) {
|
||||
case k_pair_number_number: {
|
||||
if (val_a.type() == NodeValue::k_rational &&
|
||||
val_b.type() == NodeValue::k_rational && operation != k_op_power) {
|
||||
// Preserve rationals
|
||||
output->push(
|
||||
NodeValue::k_rational,
|
||||
QVariant::fromValue(perform_add_sub_mult_div<Rational, Rational>(
|
||||
operation, val_a.to_rational(), val_b.to_rational())),
|
||||
this);
|
||||
} else {
|
||||
output->push(NodeValue::k_float,
|
||||
perform_all<float, float>(operation,
|
||||
retrieve_number(val_a),
|
||||
retrieve_number(val_b)),
|
||||
this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_vec_vec: {
|
||||
// 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
|
||||
QVector4D vec_a = retrieve_vector(val_a);
|
||||
QVector4D vec_b = retrieve_vector(val_b);
|
||||
|
||||
if (operation == k_op_divide) {
|
||||
// Lower-dimensional vectors are padded with zeros; dividing the
|
||||
// padding components would be 0/0 (assert in Qt debug builds, NaN
|
||||
// otherwise). Force those components to 0/1 so the result is a
|
||||
// well-defined zero, which is discarded by PushVector anyway.
|
||||
const NodeValue::Type max_type = qMax(val_a.type(), val_b.type());
|
||||
if (max_type == NodeValue::k_vec2) {
|
||||
vec_a.setZ(0.0f);
|
||||
vec_a.setW(0.0f);
|
||||
vec_b.setZ(1.0f);
|
||||
vec_b.setW(1.0f);
|
||||
} else if (max_type == NodeValue::k_vec3) {
|
||||
vec_a.setW(0.0f);
|
||||
vec_b.setW(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
push_vector(output, qMax(val_a.type(), val_b.type()),
|
||||
perform_add_sub_mult_div<QVector4D, QVector4D>(operation, vec_a,
|
||||
vec_b));
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_matrix_vec: {
|
||||
QMatrix4x4 matrix = (val_a.type() == NodeValue::k_matrix) ?
|
||||
val_a.to_matrix() :
|
||||
val_b.to_matrix();
|
||||
QVector4D vec = (val_a.type() == NodeValue::k_matrix) ?
|
||||
retrieve_vector(val_b) :
|
||||
retrieve_vector(val_a);
|
||||
|
||||
// Only valid operation is multiply
|
||||
push_vector(output, qMax(val_a.type(), val_b.type()),
|
||||
perform_mult<QVector4D, QMatrix4x4>(operation, vec, matrix));
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_vec_number: {
|
||||
QVector4D vec = (NodeValue::type_is_vector(val_a.type()) ?
|
||||
retrieve_vector(val_a) :
|
||||
retrieve_vector(val_b));
|
||||
float number = retrieve_number(NodeValue::type_is_vector(val_a.type()) ?
|
||||
val_b :
|
||||
val_a);
|
||||
|
||||
// Only multiply and divide are valid operations
|
||||
push_vector(output,
|
||||
NodeValue::type_is_vector(val_a.type()) ? val_a.type() :
|
||||
val_b.type(),
|
||||
perform_mult_div<QVector4D, float>(operation, vec, number));
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_matrix_matrix: {
|
||||
QMatrix4x4 mat_a = val_a.to_matrix();
|
||||
QMatrix4x4 mat_b = val_b.to_matrix();
|
||||
output->push(NodeValue::k_matrix,
|
||||
perform_add_sub_mult<QMatrix4x4, QMatrix4x4>(operation, mat_a,
|
||||
mat_b),
|
||||
this);
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_color_color: {
|
||||
Color col_a = val_a.to_color();
|
||||
Color col_b = val_b.to_color();
|
||||
|
||||
// Only add and subtract are valid operations
|
||||
output->push(NodeValue::k_color,
|
||||
QVariant::fromValue(
|
||||
perform_add_sub<Color, Color>(operation, col_a, col_b)),
|
||||
this);
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_number_color: {
|
||||
Color col = (val_a.type() == NodeValue::k_color) ? val_a.to_color() :
|
||||
val_b.to_color();
|
||||
float num = (val_a.type() == NodeValue::k_color) ? val_b.to_double() :
|
||||
val_a.to_double();
|
||||
|
||||
// Only multiply and divide are valid operations
|
||||
output->push(
|
||||
NodeValue::k_color,
|
||||
QVariant::fromValue(perform_mult<Color, float>(operation, col, num)),
|
||||
this);
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_sample_sample: {
|
||||
SampleBuffer samples_a = val_a.to_samples();
|
||||
SampleBuffer samples_b = val_b.to_samples();
|
||||
|
||||
size_t max_samples =
|
||||
qMax(samples_a.sample_count(), samples_b.sample_count());
|
||||
size_t min_samples =
|
||||
qMin(samples_a.sample_count(), samples_b.sample_count());
|
||||
|
||||
SampleBuffer mixed_samples =
|
||||
SampleBuffer(samples_a.audio_params(), max_samples);
|
||||
|
||||
for (int i = 0; i < mixed_samples.audio_params().channel_count(); i++) {
|
||||
// Mix samples that are in both buffers
|
||||
for (size_t j = 0; j < min_samples; j++) {
|
||||
mixed_samples.data(i)[j] = perform_all<float, float>(
|
||||
operation, samples_a.data(i)[j], samples_b.data(i)[j]);
|
||||
}
|
||||
}
|
||||
|
||||
if (max_samples > min_samples) {
|
||||
// Fill in remainder space with 0s
|
||||
size_t remainder = max_samples - min_samples;
|
||||
|
||||
const SampleBuffer &larger_buffer =
|
||||
(max_samples == samples_a.sample_count()) ? samples_a :
|
||||
samples_b;
|
||||
|
||||
for (int i = 0; i < mixed_samples.audio_params().channel_count();
|
||||
i++) {
|
||||
memcpy(&mixed_samples.data(i)[min_samples],
|
||||
&larger_buffer.data(i)[min_samples],
|
||||
remainder * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
output->push(NodeValue::k_samples, QVariant::fromValue(mixed_samples),
|
||||
this);
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_texture_color:
|
||||
case k_pair_texture_number:
|
||||
case k_pair_texture_texture:
|
||||
case k_pair_texture_matrix: {
|
||||
ShaderJob job;
|
||||
job.set_shader_id(QStringLiteral("%1.%2.%3.%4")
|
||||
.arg(QString::number(operation),
|
||||
QString::number(pairing),
|
||||
QString::number(val_a.type()),
|
||||
QString::number(val_b.type())));
|
||||
|
||||
job.insert(param_a_in, val_a);
|
||||
job.insert(param_b_in, val_b);
|
||||
|
||||
bool operation_is_noop = false;
|
||||
|
||||
const NodeValue &number_val =
|
||||
val_a.type() == NodeValue::k_texture ? val_b : val_a;
|
||||
const NodeValue &texture_val =
|
||||
val_a.type() == NodeValue::k_texture ? val_a : val_b;
|
||||
TexturePtr texture = texture_val.to_texture();
|
||||
|
||||
if (!texture) {
|
||||
operation_is_noop = true;
|
||||
} else if (pairing == k_pair_texture_number) {
|
||||
if (number_is_no_op(operation, retrieve_number(number_val))) {
|
||||
operation_is_noop = true;
|
||||
}
|
||||
} else if (pairing == k_pair_texture_matrix) {
|
||||
// Only allow matrix multiplication
|
||||
const QVector2D &sequence_res = globals.nonsquare_resolution();
|
||||
QVector2D texture_res(texture->params().width() *
|
||||
texture->pixel_aspect_ratio().to_double(),
|
||||
texture->params().height());
|
||||
|
||||
QMatrix4x4 adjusted_matrix =
|
||||
TransformDistortNode::adjust_matrix_by_resolutions(
|
||||
number_val.to_matrix(), sequence_res,
|
||||
texture->params().offset(), texture_res);
|
||||
|
||||
if (operation != k_op_multiply || adjusted_matrix.isIdentity()) {
|
||||
operation_is_noop = true;
|
||||
} else {
|
||||
// Replace with adjusted matrix
|
||||
job.insert(val_a.type() == NodeValue::k_texture ? param_b_in :
|
||||
param_a_in,
|
||||
NodeValue(NodeValue::k_matrix, adjusted_matrix,
|
||||
this));
|
||||
}
|
||||
}
|
||||
|
||||
if (operation_is_noop) {
|
||||
// Just push texture as-is
|
||||
output->push(texture_val);
|
||||
} else {
|
||||
// Push shader job
|
||||
output->push(NodeValue::k_texture,
|
||||
Texture::job(globals.vparams(), job), this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_sample_number: {
|
||||
// Queue a sample job
|
||||
const NodeValue &number_val =
|
||||
val_a.type() == NodeValue::k_samples ? val_b : val_a;
|
||||
const QString &number_param =
|
||||
val_a.type() == NodeValue::k_samples ? param_b_in : param_a_in;
|
||||
|
||||
float number = retrieve_number(number_val);
|
||||
|
||||
SampleBuffer buffer = val_a.type() == NodeValue::k_samples ?
|
||||
val_a.to_samples() :
|
||||
val_b.to_samples();
|
||||
|
||||
if (buffer.is_allocated()) {
|
||||
if (is_input_static(number_param)) {
|
||||
if (!number_is_no_op(operation, number)) {
|
||||
for (int i = 0; i < buffer.audio_params().channel_count();
|
||||
i++) {
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
// Use SSE instructions for optimization
|
||||
perform_all_on_float_buffer_sse(operation, buffer.data(i),
|
||||
number, 0,
|
||||
buffer.sample_count());
|
||||
#else
|
||||
PerformAllOnFloatBuffer(operation, buffer.data(i),
|
||||
number, 0,
|
||||
buffer.sample_count());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
output->push(NodeValue::k_samples, QVariant::fromValue(buffer),
|
||||
this);
|
||||
} else {
|
||||
SampleJob job(globals.time(),
|
||||
val_a.type() == NodeValue::k_samples ? val_a :
|
||||
val_b);
|
||||
job.insert(number_param,
|
||||
NodeValue(NodeValue::k_float, number, this));
|
||||
output->push(NodeValue::k_samples, QVariant::fromValue(job),
|
||||
this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case k_pair_none:
|
||||
case k_pair_count:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MathNodeBase::process_samples_internal(const NodeValueRow &values,
|
||||
MathNodeBase::Operation operation,
|
||||
const QString ¶m_a_in,
|
||||
const QString ¶m_b_in,
|
||||
const olive::SampleBuffer &input,
|
||||
olive::SampleBuffer &output,
|
||||
int index) const
|
||||
{
|
||||
// This function is only used for sample+number pairing
|
||||
NodeValue number_val = values[param_a_in];
|
||||
|
||||
if (number_val.type() == NodeValue::k_none) {
|
||||
number_val = values[param_b_in];
|
||||
|
||||
if (number_val.type() == NodeValue::k_none) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float number_flt = retrieve_number(number_val);
|
||||
|
||||
for (int i = 0; i < output.audio_params().channel_count(); i++) {
|
||||
output.data(i)[index] = perform_all<float, float>(
|
||||
operation, input.data(i)[index], number_flt);
|
||||
}
|
||||
}
|
||||
|
||||
float MathNodeBase::retrieve_number(const NodeValue &val)
|
||||
{
|
||||
if (val.type() == NodeValue::k_rational) {
|
||||
return val.to_rational().to_double();
|
||||
} else {
|
||||
return val.to_double();
|
||||
}
|
||||
}
|
||||
|
||||
bool MathNodeBase::number_is_no_op(const MathNodeBase::Operation &op,
|
||||
const float &number)
|
||||
{
|
||||
switch (op) {
|
||||
case k_op_add:
|
||||
case k_op_subtract:
|
||||
if (qIsNull(number)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case k_op_multiply:
|
||||
case k_op_divide:
|
||||
case k_op_power:
|
||||
if (qFuzzyCompare(number, 1.0f)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
MathNodeBase::PairingCalculator::PairingCalculator(
|
||||
const NodeValueTable &table_a, const NodeValueTable &table_b)
|
||||
{
|
||||
QVector<int> pair_likelihood_a = get_pair_likelihood(table_a);
|
||||
QVector<int> pair_likelihood_b = get_pair_likelihood(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<int> likelihoods(k_pair_count);
|
||||
|
||||
for (int i = 0; i < k_pair_count; i++) {
|
||||
if (pair_likelihood_a.at(i) == -1 || pair_likelihood_b.at(i) == -1) {
|
||||
likelihoods.replace(i, -1);
|
||||
} else {
|
||||
likelihoods.replace(i, pair_likelihood_a.at(i) + weight_a +
|
||||
pair_likelihood_b.at(i) + weight_b);
|
||||
}
|
||||
}
|
||||
|
||||
most_likely_pairing_ = k_pair_none;
|
||||
|
||||
for (int i = 0; i < likelihoods.size(); i++) {
|
||||
if (likelihoods.at(i) > -1) {
|
||||
if (most_likely_pairing_ == k_pair_none ||
|
||||
likelihoods.at(i) > likelihoods.at(most_likely_pairing_)) {
|
||||
most_likely_pairing_ = static_cast<Pairing>(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (most_likely_pairing_ != k_pair_none) {
|
||||
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<int>
|
||||
MathNodeBase::PairingCalculator::get_pair_likelihood(const NodeValueTable &table)
|
||||
{
|
||||
QVector<int> likelihood(k_pair_count, -1);
|
||||
|
||||
for (int i = 0; i < table.count(); i++) {
|
||||
NodeValue::Type type = table.at(i).type();
|
||||
|
||||
int weight = i;
|
||||
|
||||
if (NodeValue::type_is_vector(type)) {
|
||||
likelihood.replace(k_pair_vec_vec, weight);
|
||||
likelihood.replace(k_pair_vec_number, weight);
|
||||
likelihood.replace(k_pair_matrix_vec, weight);
|
||||
} else if (type == NodeValue::k_matrix) {
|
||||
likelihood.replace(k_pair_matrix_matrix, weight);
|
||||
likelihood.replace(k_pair_matrix_vec, weight);
|
||||
likelihood.replace(k_pair_texture_matrix, weight);
|
||||
} else if (type == NodeValue::k_color) {
|
||||
likelihood.replace(k_pair_color_color, weight);
|
||||
likelihood.replace(k_pair_number_color, weight);
|
||||
likelihood.replace(k_pair_texture_color, weight);
|
||||
} else if (NodeValue::type_is_numeric(type)) {
|
||||
likelihood.replace(k_pair_number_number, weight);
|
||||
likelihood.replace(k_pair_vec_number, weight);
|
||||
likelihood.replace(k_pair_number_color, weight);
|
||||
likelihood.replace(k_pair_texture_number, weight);
|
||||
likelihood.replace(k_pair_sample_number, weight);
|
||||
} else if (type == NodeValue::k_samples) {
|
||||
likelihood.replace(k_pair_sample_sample, weight);
|
||||
likelihood.replace(k_pair_sample_number, weight);
|
||||
} else if (type == NodeValue::k_texture) {
|
||||
likelihood.replace(k_pair_texture_texture, weight);
|
||||
likelihood.replace(k_pair_texture_number, weight);
|
||||
likelihood.replace(k_pair_texture_color, weight);
|
||||
likelihood.replace(k_pair_texture_matrix, weight);
|
||||
}
|
||||
}
|
||||
|
||||
return likelihood;
|
||||
}
|
||||
|
||||
bool MathNodeBase::PairingCalculator::found_most_likely_pairing() const
|
||||
{
|
||||
return (most_likely_pairing_ > k_pair_none &&
|
||||
most_likely_pairing_ < k_pair_count);
|
||||
}
|
||||
|
||||
MathNodeBase::Pairing
|
||||
MathNodeBase::PairingCalculator::get_most_likely_pairing() const
|
||||
{
|
||||
return most_likely_pairing_;
|
||||
}
|
||||
|
||||
const NodeValue &MathNodeBase::PairingCalculator::get_most_likely_value_a() const
|
||||
{
|
||||
return most_likely_value_a_;
|
||||
}
|
||||
|
||||
const NodeValue &MathNodeBase::PairingCalculator::get_most_likely_value_b() const
|
||||
{
|
||||
return most_likely_value_b_;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
T MathNodeBase::perform_all(Operation operation, T a, U b)
|
||||
{
|
||||
switch (operation) {
|
||||
case k_op_add:
|
||||
return a + b;
|
||||
case k_op_subtract:
|
||||
return a - b;
|
||||
case k_op_multiply:
|
||||
return a * b;
|
||||
case k_op_divide:
|
||||
return a / b;
|
||||
case k_op_power:
|
||||
return std::pow(a, b);
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
T MathNodeBase::perform_mult_div(Operation operation, T a, U b)
|
||||
{
|
||||
switch (operation) {
|
||||
case k_op_multiply:
|
||||
return a * b;
|
||||
case k_op_divide:
|
||||
return a / b;
|
||||
case k_op_add:
|
||||
case k_op_subtract:
|
||||
case k_op_power:
|
||||
break;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
T MathNodeBase::perform_add_sub(Operation operation, T a, U b)
|
||||
{
|
||||
switch (operation) {
|
||||
case k_op_add:
|
||||
return a + b;
|
||||
case k_op_subtract:
|
||||
return a - b;
|
||||
case k_op_multiply:
|
||||
case k_op_divide:
|
||||
case k_op_power:
|
||||
break;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
T MathNodeBase::perform_mult(Operation operation, T a, U b)
|
||||
{
|
||||
switch (operation) {
|
||||
case k_op_multiply:
|
||||
return a * b;
|
||||
case k_op_add:
|
||||
case k_op_subtract:
|
||||
case k_op_divide:
|
||||
case k_op_power:
|
||||
break;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
T MathNodeBase::perform_add_sub_mult(Operation operation, T a, U b)
|
||||
{
|
||||
switch (operation) {
|
||||
case k_op_add:
|
||||
return a + b;
|
||||
case k_op_subtract:
|
||||
return a - b;
|
||||
case k_op_multiply:
|
||||
return a * b;
|
||||
case k_op_divide:
|
||||
case k_op_power:
|
||||
break;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
T MathNodeBase::perform_add_sub_mult_div(Operation operation, T a, U b)
|
||||
{
|
||||
switch (operation) {
|
||||
case k_op_add:
|
||||
return a + b;
|
||||
case k_op_subtract:
|
||||
return a - b;
|
||||
case k_op_multiply:
|
||||
return a * b;
|
||||
case k_op_divide:
|
||||
return a / b;
|
||||
case k_op_power:
|
||||
break;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_MATHNODEBASE_H
|
||||
#define OAK_MATHNODEBASE_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class MathNodeBase : public Node {
|
||||
public:
|
||||
MathNodeBase() = default;
|
||||
|
||||
enum Operation { k_op_add, k_op_subtract, k_op_multiply, k_op_divide, k_op_power };
|
||||
|
||||
static QString get_operation_name(Operation o);
|
||||
|
||||
protected:
|
||||
enum Pairing {
|
||||
k_pair_none = -1,
|
||||
|
||||
k_pair_number_number,
|
||||
k_pair_vec_vec,
|
||||
k_pair_matrix_matrix,
|
||||
k_pair_color_color,
|
||||
k_pair_texture_texture,
|
||||
|
||||
k_pair_vec_number,
|
||||
k_pair_matrix_vec,
|
||||
k_pair_number_color,
|
||||
k_pair_texture_number,
|
||||
k_pair_texture_color,
|
||||
k_pair_texture_matrix,
|
||||
k_pair_sample_sample,
|
||||
k_pair_sample_number,
|
||||
|
||||
k_pair_count
|
||||
};
|
||||
|
||||
class PairingCalculator {
|
||||
public:
|
||||
PairingCalculator(const NodeValueTable &table_a,
|
||||
const NodeValueTable &table_b);
|
||||
|
||||
bool found_most_likely_pairing() const;
|
||||
Pairing get_most_likely_pairing() const;
|
||||
|
||||
const NodeValue &get_most_likely_value_a() const;
|
||||
const NodeValue &get_most_likely_value_b() const;
|
||||
|
||||
private:
|
||||
static QVector<int> get_pair_likelihood(const NodeValueTable &table);
|
||||
|
||||
Pairing most_likely_pairing_;
|
||||
|
||||
NodeValue most_likely_value_a_;
|
||||
|
||||
NodeValue most_likely_value_b_;
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
static T perform_all(Operation operation, T a, U b);
|
||||
|
||||
template <typename T, typename U>
|
||||
static T perform_mult_div(Operation operation, T a, U b);
|
||||
|
||||
template <typename T, typename U>
|
||||
static T perform_add_sub(Operation operation, T a, U b);
|
||||
|
||||
template <typename T, typename U>
|
||||
static T perform_mult(Operation operation, T a, U b);
|
||||
|
||||
template <typename T, typename U>
|
||||
static T perform_add_sub_mult(Operation operation, T a, U b);
|
||||
|
||||
template <typename T, typename U>
|
||||
static T perform_add_sub_mult_div(Operation operation, T a, U b);
|
||||
|
||||
static void perform_all_on_float_buffer(Operation operation, float *a, float b,
|
||||
int start, int end);
|
||||
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
static void perform_all_on_float_buffer_sse(Operation operation, float *a,
|
||||
float b, int start, int end);
|
||||
#endif
|
||||
|
||||
static QString get_shader_uniform_type(const NodeValue::Type &type);
|
||||
|
||||
static QString get_shader_variable_call(const QString &input_id,
|
||||
const NodeValue::Type &type,
|
||||
const QString &coord_op = QString());
|
||||
|
||||
static QVector4D retrieve_vector(const NodeValue &val);
|
||||
|
||||
static float retrieve_number(const NodeValue &val);
|
||||
|
||||
static bool number_is_no_op(const Operation &op, const float &number);
|
||||
|
||||
ShaderCode get_shader_code_internal(const QString &shader_id,
|
||||
const QString ¶m_a_in,
|
||||
const QString ¶m_b_in) const;
|
||||
|
||||
void push_vector(NodeValueTable *output, NodeValue::Type type,
|
||||
const QVector4D &vec) const;
|
||||
|
||||
void value_internal(Operation operation, Pairing pairing,
|
||||
const QString ¶m_a_in, const NodeValue &val_a,
|
||||
const QString ¶m_b_in, const NodeValue &val_b,
|
||||
const NodeGlobals &globals,
|
||||
NodeValueTable *output) const;
|
||||
|
||||
void process_samples_internal(const NodeValueRow &values, Operation operation,
|
||||
const QString ¶m_a_in,
|
||||
const QString ¶m_b_in,
|
||||
const SampleBuffer &input, SampleBuffer &output,
|
||||
int index) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_MATHNODEBASE_H
|
||||
Reference in New Issue
Block a user