From dd7af0e6bd516ec988a15619e071022edc01d467 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 11 Jun 2020 21:31:11 +1000 Subject: [PATCH] nodes: minor overhaul to functionality The nodes now have more control over how their accelerated shaders/sample functions are run, as well as how items are popped off the value tables. This allows for various optimizations that we didn't have access to before. --- app/core.cpp | 4 + app/node/audio/pan/pan.cpp | 22 +- app/node/audio/pan/pan.h | 6 +- app/node/audio/volume/volume.cpp | 25 +- app/node/audio/volume/volume.h | 11 +- app/node/filter/blur/blur.cpp | 53 ++- app/node/filter/blur/blur.h | 7 +- app/node/filter/stroke/stroke.cpp | 29 +- app/node/filter/stroke/stroke.h | 4 +- app/node/generator/matrix/matrix.cpp | 8 +- app/node/generator/matrix/matrix.h | 8 +- app/node/generator/polygon/polygon.cpp | 27 +- app/node/generator/polygon/polygon.h | 10 +- app/node/generator/solid/solid.cpp | 15 +- app/node/generator/solid/solid.h | 4 +- app/node/input/media/media.cpp | 8 +- app/node/math/math/math.cpp | 202 ++++----- app/node/math/math/math.h | 13 +- app/node/math/merge/merge.cpp | 31 +- app/node/math/merge/merge.h | 4 +- app/node/node.cpp | 59 +-- app/node/node.h | 49 +-- app/node/param.cpp | 2 + app/node/param.h | 18 + app/node/traverser.cpp | 131 +++++- app/node/traverser.h | 18 +- app/node/value.cpp | 14 +- app/node/value.h | 6 +- app/render/CMakeLists.txt | 1 + app/render/ShaderInfo.h | 4 - app/render/backend/opengl/CMakeLists.txt | 1 - app/render/backend/opengl/openglproxy.cpp | 390 ++++++++++-------- app/render/backend/opengl/openglproxy.h | 8 +- app/render/backend/opengl/openglworker.cpp | 9 +- app/render/backend/opengl/openglworker.h | 4 +- app/render/backend/renderworker.cpp | 265 +++++------- app/render/backend/renderworker.h | 16 +- app/render/framehashcache.cpp | 12 +- app/render/shaderinfo.h | 157 +++++++ app/shaders/blur.frag | 41 +- .../nodeparamviewwidgetbridge.cpp | 6 + app/widget/viewer/gizmotraverser.cpp | 30 +- app/widget/viewer/gizmotraverser.h | 14 +- app/widget/viewer/viewerdisplay.cpp | 2 +- 44 files changed, 1051 insertions(+), 697 deletions(-) delete mode 100644 app/render/ShaderInfo.h create mode 100644 app/render/shaderinfo.h diff --git a/app/core.cpp b/app/core.cpp index fe560b3dd..b5a86463b 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -49,6 +49,7 @@ #include "render/colormanager.h" #include "render/diskmanager.h" #include "render/pixelformat.h" +#include "render/shaderinfo.h" #include "task/cache/cache.h" #include "task/project/import/import.h" #include "task/project/load/load.h" @@ -542,6 +543,7 @@ void Core::DeclareTypesForQt() qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); @@ -553,6 +555,8 @@ void Core::DeclareTypesForQt() qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); } void Core::StartGUI(bool full_screen) diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index 2cc83c4c1..e97f08f67 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -59,17 +59,23 @@ QString PanNode::Description() const return tr("Adjust the stereo panning of an audio source."); } -Node::Capabilities PanNode::GetCapabilities(const NodeValueDatabase &) const +NodeValueTable PanNode::Value(NodeValueDatabase &value) const { - return kSampleProcessor; + // Create a sample job + SampleJob job(samples_input_); + job.InsertValue(panning_input_, value); + + // Push it to our table + NodeValueTable table = value.Merge(); + + if (!qIsNull(job.GetValue(samples_input_).data().toDouble())) { + table.Push(NodeParam::kSampleJob, QVariant::fromValue(job), this); + } + + return table; } -NodeInput *PanNode::ProcessesSamplesFrom(const NodeValueDatabase &) const -{ - return samples_input_; -} - -void PanNode::ProcessSamples(const NodeValueDatabase &values, const AudioParams ¶ms, const SampleBufferPtr input, SampleBufferPtr output, int index) const +void PanNode::ProcessSamples(NodeValueDatabase &values, const AudioParams ¶ms, const SampleBufferPtr input, SampleBufferPtr output, int index) const { if (params.channel_count() != 2) { // This node currently only works for stereo audio diff --git a/app/node/audio/pan/pan.h b/app/node/audio/pan/pan.h index 3bb13c54d..87195d538 100644 --- a/app/node/audio/pan/pan.h +++ b/app/node/audio/pan/pan.h @@ -37,9 +37,9 @@ public: virtual QList Category() const override; virtual QString Description() const override; - virtual Capabilities GetCapabilities(const NodeValueDatabase&) const override; - virtual NodeInput* ProcessesSamplesFrom(const NodeValueDatabase &value) const override; - virtual void ProcessSamples(const NodeValueDatabase& values, const AudioParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const override; + virtual NodeValueTable Value(NodeValueDatabase &value) const override; + + virtual void ProcessSamples(NodeValueDatabase &values, const AudioParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const override; virtual void Retranslate() override; diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 1cc38475a..5988f3c6c 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -58,17 +58,21 @@ QString VolumeNode::Description() const return tr("Adjusts the volume of an audio source."); } -Node::Capabilities VolumeNode::GetCapabilities(const NodeValueDatabase &) const +NodeValueTable VolumeNode::Value(NodeValueDatabase &value) const { - return kSampleProcessor; + SampleJob job(samples_input_); + job.InsertValue(volume_input_, value); + + NodeValueTable table = value.Merge(); + + if (qFuzzyCompare(job.GetValue(volume_input_).data().toDouble(), 1.0)) { + table.Push(NodeParam::kSampleJob, QVariant::fromValue(job), this); + } + + return table; } -NodeInput *VolumeNode::ProcessesSamplesFrom(const NodeValueDatabase &) const -{ - return samples_input_; -} - -void VolumeNode::ProcessSamples(const NodeValueDatabase &values, const AudioParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const +void VolumeNode::ProcessSamples(NodeValueDatabase &values, const AudioParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const { float volume_val = values[volume_input_].Get(NodeParam::kFloat).toFloat(); @@ -83,9 +87,4 @@ void VolumeNode::Retranslate() volume_input_->set_name(tr("Volume")); } -NodeInput *VolumeNode::samples_input() const -{ - return samples_input_; -} - OLIVE_NAMESPACE_EXIT diff --git a/app/node/audio/volume/volume.h b/app/node/audio/volume/volume.h index e4d37d620..3b331205e 100644 --- a/app/node/audio/volume/volume.h +++ b/app/node/audio/volume/volume.h @@ -37,13 +37,16 @@ public: virtual QList Category() const override; virtual QString Description() const override; - virtual Capabilities GetCapabilities(const NodeValueDatabase&) const override; - virtual NodeInput* ProcessesSamplesFrom(const NodeValueDatabase &value) const override; - virtual void ProcessSamples(const NodeValueDatabase& values, const AudioParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const override; + virtual NodeValueTable Value(NodeValueDatabase &value) const override; + + virtual void ProcessSamples(NodeValueDatabase &values, const AudioParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const override; virtual void Retranslate() override; - NodeInput* samples_input() const; + NodeInput* samples_input() const + { + return samples_input_; + } private: NodeInput* samples_input_; diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index 64d634d0c..91d5791d9 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -80,25 +80,52 @@ void BlurFilterNode::Retranslate() repeat_edge_pixels_input_->set_name(tr("Repeat Edge Pixels")); } -Node::Capabilities BlurFilterNode::GetCapabilities(const NodeValueDatabase &) const +ShaderCode BlurFilterNode::GetShaderCode(const QByteArray &shader_id) const { - return kShader; + Q_UNUSED(shader_id) + return ShaderCode(ReadFileAsString(":/shaders/blur.frag"), QString()); } -QString BlurFilterNode::ShaderFragmentCode(const NodeValueDatabase &) const +NodeValueTable BlurFilterNode::Value(NodeValueDatabase &value) const { - return ReadFileAsString(":/shaders/blur.frag"); -} + ShaderJob job; -int BlurFilterNode::ShaderIterations() const -{ - // FIXME: Optimize if horiz_in or vert_in is disabled - return 2; -} + job.InsertValue(texture_input_, value); + job.InsertValue(method_input_, value); + job.InsertValue(radius_input_, value); + job.InsertValue(horiz_input_, value); + job.InsertValue(vert_input_, value); + job.InsertValue(repeat_edge_pixels_input_, value); -NodeInput *BlurFilterNode::ShaderIterativeInput() const -{ - return texture_input_; + NodeValueTable table = value.Merge(); + + // If there's no texture, no need to run an operation + if (!job.GetValue(texture_input_).data().isNull()) { + + // Check if radius > 0, and both "horiz" and/or "vert" are enabled + if ((job.GetValue(horiz_input_).data().toBool() || job.GetValue(vert_input_).data().toBool()) + && job.GetValue(radius_input_).data().toDouble() > 0.0) { + + // Set iteration count to 2 if we're blurring both horizontally and vertically + if (job.GetValue(horiz_input_).data().toBool() && job.GetValue(vert_input_).data().toBool()) { + job.SetIterations(2, texture_input_); + } + + // If we're not repeating pixels, expect an alpha channel to appear + if (!job.GetValue(repeat_edge_pixels_input_).data().toBool()) { + job.SetAlphaChannelRequired(true); + } + + table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); + + } else { + // If we're not performing the blur job, just push the texture + table.Push(job.GetValue(texture_input_)); + } + + } + + return table; } OLIVE_NAMESPACE_EXIT diff --git a/app/node/filter/blur/blur.h b/app/node/filter/blur/blur.h index 142f03985..d03c3732c 100644 --- a/app/node/filter/blur/blur.h +++ b/app/node/filter/blur/blur.h @@ -39,11 +39,8 @@ public: virtual void Retranslate() override; - virtual Capabilities GetCapabilities(const NodeValueDatabase&) const override; - virtual QString ShaderFragmentCode(const NodeValueDatabase&) const override; - - virtual int ShaderIterations() const override; - virtual NodeInput* ShaderIterativeInput() const override; + virtual ShaderCode GetShaderCode(const QByteArray &shader_id) const override; + virtual NodeValueTable Value(NodeValueDatabase &value) const override; private: NodeInput* texture_input_; diff --git a/app/node/filter/stroke/stroke.cpp b/app/node/filter/stroke/stroke.cpp index e87449db2..629b985b5 100644 --- a/app/node/filter/stroke/stroke.cpp +++ b/app/node/filter/stroke/stroke.cpp @@ -82,14 +82,35 @@ void StrokeFilterNode::Retranslate() inner_input_->set_name(tr("Inner")); } -Node::Capabilities StrokeFilterNode::GetCapabilities(const NodeValueDatabase &) const +NodeValueTable StrokeFilterNode::Value(NodeValueDatabase &value) const { - return kShader; + ShaderJob job; + + job.InsertValue(tex_input_, value); + job.InsertValue(color_input_, value); + job.InsertValue(radius_input_, value); + job.InsertValue(opacity_input_, value); + job.InsertValue(inner_input_, value); + + NodeValueTable table = value.Merge(); + + if (!job.GetValue(tex_input_).data().isNull()) { + if (job.GetValue(radius_input_).data().toDouble() > 0.0 + && job.GetValue(opacity_input_).data().toDouble() > 0.0) { + table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); + } else { + table.Push(job.GetValue(tex_input_)); + } + } + + return table; } -QString StrokeFilterNode::ShaderFragmentCode(const NodeValueDatabase &) const +ShaderCode StrokeFilterNode::GetShaderCode(const QByteArray &shader_id) const { - return ReadFileAsString(":/shaders/stroke.frag"); + Q_UNUSED(shader_id) + + return ShaderCode(ReadFileAsString(":/shaders/stroke.frag"), QString()); } OLIVE_NAMESPACE_EXIT diff --git a/app/node/filter/stroke/stroke.h b/app/node/filter/stroke/stroke.h index 06eb97f64..7a9d0977a 100644 --- a/app/node/filter/stroke/stroke.h +++ b/app/node/filter/stroke/stroke.h @@ -39,8 +39,8 @@ public: virtual void Retranslate() override; - virtual Capabilities GetCapabilities(const NodeValueDatabase&) const override; - virtual QString ShaderFragmentCode(const NodeValueDatabase&) const override; + virtual NodeValueTable Value(NodeValueDatabase &value) const override; + virtual ShaderCode GetShaderCode(const QByteArray &shader_id) const override; private: NodeInput* tex_input_; diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index 5e7116df3..5dbfe7377 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -100,7 +100,7 @@ NodeValueTable MatrixGenerator::Value(NodeValueDatabase &value) const return output; } -bool MatrixGenerator::GizmoPress(const NodeValueDatabase &db, const QPointF &p, const QVector2D &scale, const QSize &viewport) +bool MatrixGenerator::GizmoPress(NodeValueDatabase &db, const QPointF &p, const QVector2D &scale, const QSize &viewport) { GizmoSharedData gizmo_data(viewport, scale); @@ -167,7 +167,7 @@ bool MatrixGenerator::HasGizmos() const return true; } -void MatrixGenerator::DrawGizmos(const NodeValueDatabase &db, QPainter *p, const QVector2D &scale, const QSize& viewport) const +void MatrixGenerator::DrawGizmos(NodeValueDatabase &db, QPainter *p, const QVector2D &scale, const QSize& viewport) const { p->setPen(Qt::white); @@ -219,7 +219,7 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(NodeValueDatabase &value) const value[anchor_input_].Take(NodeParam::kVec2).value()); } -QMatrix4x4 MatrixGenerator::GenerateMatrix(const NodeValueDatabase &value, bool ignore_anchor) const +QMatrix4x4 MatrixGenerator::GenerateMatrix(NodeValueDatabase &value, bool ignore_anchor) const { QVector2D anchor; @@ -262,7 +262,7 @@ QMatrix4x4 MatrixGenerator::GenerateMatrix(const QVector2D& pos, return mat; } -QPointF MatrixGenerator::GetGizmoAnchorPoint(const NodeValueDatabase &db, +QPointF MatrixGenerator::GetGizmoAnchorPoint(NodeValueDatabase &db, const GizmoSharedData& gizmo_data) const { QMatrix4x4 matrix; diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h index 453f8eb76..d6418eda8 100644 --- a/app/node/generator/matrix/matrix.h +++ b/app/node/generator/matrix/matrix.h @@ -47,9 +47,9 @@ public: virtual NodeValueTable Value(NodeValueDatabase& value) const override; virtual bool HasGizmos() const override; - virtual void DrawGizmos(const NodeValueDatabase& db, QPainter *p, const QVector2D &scale, const QSize& viewport) const override; + virtual void DrawGizmos(NodeValueDatabase& db, QPainter *p, const QVector2D &scale, const QSize& viewport) const override; - virtual bool GizmoPress(const NodeValueDatabase& db, const QPointF &p, const QVector2D &scale, const QSize& viewport) override; + virtual bool GizmoPress(NodeValueDatabase& db, const QPointF &p, const QVector2D &scale, const QSize& viewport) override; virtual void GizmoMove(const QPointF &p, const QVector2D &scale, const rational &time) override; virtual void GizmoRelease() override; @@ -63,14 +63,14 @@ private: }; QMatrix4x4 GenerateMatrix(NodeValueDatabase& value) const; - QMatrix4x4 GenerateMatrix(const NodeValueDatabase& value, bool ignore_anchor) const; + QMatrix4x4 GenerateMatrix(NodeValueDatabase &value, bool ignore_anchor) const; static QMatrix4x4 GenerateMatrix(const QVector2D &pos, const float &rot, const QVector2D &scale, bool uniform_scale, const QVector2D &anchor); - QPointF GetGizmoAnchorPoint(const NodeValueDatabase &db, const GizmoSharedData &gizmo_data) const; + QPointF GetGizmoAnchorPoint(NodeValueDatabase &db, const GizmoSharedData &gizmo_data) const; static int GetGizmoAnchorPointRadius(); NodeInput* gizmo_drag_; diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index 9bb9e02e4..0ff9614f1 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -85,14 +85,23 @@ void PolygonGenerator::Retranslate() color_input_->set_name(tr("Color")); } -Node::Capabilities PolygonGenerator::GetCapabilities(const NodeValueDatabase &) const +ShaderCode PolygonGenerator::GetShaderCode(const QByteArray &shader_id) const { - return kShader; + Q_UNUSED(shader_id) + + return ShaderCode(Node::ReadFileAsString(":/shaders/polygon.frag"), QString()); } -QString PolygonGenerator::ShaderFragmentCode(const NodeValueDatabase &) const +NodeValueTable PolygonGenerator::Value(NodeValueDatabase &value) const { - return Node::ReadFileAsString(":/shaders/polygon.frag"); + ShaderJob job; + + job.InsertValue(points_input_, value); + job.InsertValue(color_input_, value); + + NodeValueTable table = value.Merge(); + table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); + return table; } bool PolygonGenerator::HasGizmos() const @@ -100,8 +109,10 @@ bool PolygonGenerator::HasGizmos() const return true; } -void PolygonGenerator::DrawGizmos(const NodeValueDatabase &db, QPainter *p, const QVector2D &scale, const QSize &viewport) const +void PolygonGenerator::DrawGizmos(NodeValueDatabase &db, QPainter *p, const QVector2D &scale, const QSize &viewport) const { + Q_UNUSED(viewport) + if (!points_input_->GetSize()) { return; } @@ -118,8 +129,10 @@ void PolygonGenerator::DrawGizmos(const NodeValueDatabase &db, QPainter *p, cons p->drawRects(rects); } -bool PolygonGenerator::GizmoPress(const NodeValueDatabase &db, const QPointF &p, const QVector2D &scale, const QSize& viewport) +bool PolygonGenerator::GizmoPress(NodeValueDatabase &db, const QPointF &p, const QVector2D &scale, const QSize& viewport) { + Q_UNUSED(viewport) + QVector points = GetGizmoCoordinates(db, scale); QVector rects = GetGizmoRects(points); @@ -158,7 +171,7 @@ void PolygonGenerator::GizmoRelease() gizmo_y_dragger_.End(); } -QVector PolygonGenerator::GetGizmoCoordinates(const NodeValueDatabase &db, const QVector2D& scale) const +QVector PolygonGenerator::GetGizmoCoordinates(NodeValueDatabase &db, const QVector2D& scale) const { QVector points(points_input_->GetSize()); diff --git a/app/node/generator/polygon/polygon.h b/app/node/generator/polygon/polygon.h index 01398eff4..9e0b33c1e 100644 --- a/app/node/generator/polygon/polygon.h +++ b/app/node/generator/polygon/polygon.h @@ -40,18 +40,18 @@ public: virtual void Retranslate() override; - virtual Capabilities GetCapabilities(const NodeValueDatabase&) const override; - virtual QString ShaderFragmentCode(const NodeValueDatabase&) const override; + virtual ShaderCode GetShaderCode(const QByteArray& shader_id) const override; + virtual NodeValueTable Value(NodeValueDatabase &value) const override; virtual bool HasGizmos() const override; - virtual void DrawGizmos(const NodeValueDatabase& db, QPainter *p, const QVector2D &scale, const QSize& viewport) const override; + virtual void DrawGizmos(NodeValueDatabase& db, QPainter *p, const QVector2D &scale, const QSize& viewport) const override; - virtual bool GizmoPress(const NodeValueDatabase& db, const QPointF &p, const QVector2D &scale, const QSize& viewport) override; + virtual bool GizmoPress(NodeValueDatabase &db, const QPointF &p, const QVector2D &scale, const QSize& viewport) override; virtual void GizmoMove(const QPointF &p, const QVector2D &scale, const rational &time) override; virtual void GizmoRelease() override; private: - QVector GetGizmoCoordinates(const NodeValueDatabase &db, const QVector2D &scale) const; + QVector GetGizmoCoordinates(NodeValueDatabase &db, const QVector2D &scale) const; QVector GetGizmoRects(const QVector& points) const; diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index d2ef7fd74..f5a640d64 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -63,14 +63,21 @@ void SolidGenerator::Retranslate() color_input_->set_name(tr("Color")); } -Node::Capabilities SolidGenerator::GetCapabilities(const NodeValueDatabase &) const +NodeValueTable SolidGenerator::Value(NodeValueDatabase &value) const { - return kShader; + ShaderJob job; + job.InsertValue(color_input_, value); + + NodeValueTable table = value.Merge(); + table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); + return table; } -QString SolidGenerator::ShaderFragmentCode(const NodeValueDatabase &) const +ShaderCode SolidGenerator::GetShaderCode(const QByteArray &shader_id) const { - return ReadFileAsString(":/shaders/solid.frag"); + Q_UNUSED(shader_id) + + return ShaderCode(ReadFileAsString(":/shaders/solid.frag"), QString()); } OLIVE_NAMESPACE_EXIT diff --git a/app/node/generator/solid/solid.h b/app/node/generator/solid/solid.h index 8e5433e69..9b9b81a27 100644 --- a/app/node/generator/solid/solid.h +++ b/app/node/generator/solid/solid.h @@ -39,8 +39,8 @@ public: virtual void Retranslate() override; - virtual Capabilities GetCapabilities(const NodeValueDatabase&) const override; - virtual QString ShaderFragmentCode(const NodeValueDatabase&) const override; + virtual NodeValueTable Value(NodeValueDatabase &value) const override; + virtual ShaderCode GetShaderCode(const QByteArray &shader_id) const override; private: NodeInput* color_input_; diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index 839cb3d29..c0e1b3feb 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -57,7 +57,7 @@ void MediaInput::Retranslate() NodeValueTable MediaInput::Value(NodeValueDatabase &value) const { - NodeValueTable table; + NodeValueTable table = value.Merge(); if (connected_footage_) { rational media_duration = Timecode::timestamp_to_time(connected_footage_->duration(), @@ -66,12 +66,6 @@ NodeValueTable MediaInput::Value(NodeValueDatabase &value) const table.Push(NodeInput::kRational, QVariant::fromValue(media_duration), this, "length"); } - // Push buffer to the top of the stack - NodeValue buffer = value[footage_input_].GetWithMeta(NodeParam::kBuffer); - if (buffer.type() != NodeParam::kNone) { - table.Push(buffer); - } - return table; } diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp index 4839db828..79e455dfa 100644 --- a/app/node/math/math/math.cpp +++ b/app/node/math/math/math.cpp @@ -89,45 +89,21 @@ void MathNode::Retranslate() method_in_->set_combobox_strings(operations); } -Node::Capabilities MathNode::GetCapabilities(const NodeValueDatabase &input) const +ShaderCode MathNode::GetShaderCode(const QByteArray &shader_id) const { - PairingCalculator calc(input[param_a_in_], input[param_b_in_]); + QDataStream data(shader_id); - switch (calc.GetMostLikelyPairing()) { - case kPairTextureColor: - case kPairTextureNumber: - case kPairTextureTexture: - case kPairTextureMatrix: - return kShader; - case kPairSampleNumber: - return kSampleProcessor; - default: - return kNormal; - } -} + Pairing pairing; + NodeParam::DataType type_a; + NodeParam::DataType type_b; -QString MathNode::ShaderID(const NodeValueDatabase &input) const -{ - QString method = QString::number(GetOperation()); + data >> pairing; + data >> type_a; + data >> type_b; - PairingCalculator calc(input[param_a_in_], input[param_b_in_]); + QString operation, frag, vert; - QString type_a = QString::number(calc.GetMostLikelyValueA().type()); - QString type_b = QString::number(calc.GetMostLikelyValueB().type()); - - return id().append(method).append(type_a).append(type_b); -} - -QString MathNode::ShaderFragmentCode(const NodeValueDatabase &input) const -{ - PairingCalculator calc(input[param_a_in_], input[param_b_in_]); - - NodeParam::DataType type_a = calc.GetMostLikelyValueA().type(); - NodeParam::DataType type_b = calc.GetMostLikelyValueB().type(); - - QString operation; - - if (calc.GetMostLikelyPairing() == kPairTextureMatrix && GetOperation() == kOpMultiply) { + if (pairing == kPairTextureMatrix && GetOperation() == 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_; @@ -135,6 +111,11 @@ QString MathNode::ShaderFragmentCode(const NodeValueDatabase &input) const // 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 (GetOperation()) { case kOpAdd: @@ -158,7 +139,7 @@ QString MathNode::ShaderFragmentCode(const NodeValueDatabase &input) const GetShaderVariableCall(param_b_in_->id(), type_b)); } - return QStringLiteral("#version 150\n" + frag = QStringLiteral("#version 150\n" "\n" "uniform %1 %3;\n" "uniform %2 %4;\n" @@ -174,44 +155,8 @@ QString MathNode::ShaderFragmentCode(const NodeValueDatabase &input) const param_a_in_->id(), param_b_in_->id(), operation); -} -QString MathNode::ShaderVertexCode(const NodeValueDatabase &input) const -{ - PairingCalculator calc(input[param_a_in_], input[param_b_in_]); - - if (calc.GetMostLikelyPairing() == kPairTextureMatrix && GetOperation() == kOpMultiply) { - - NodeParam::DataType type_a = calc.GetMostLikelyValueA().type(); - - // 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_; - - return ReadFileAsString(":/shaders/matrix.vert").arg(mat_in->id(), tex_in->id()); - - } - - return QString(); -} - -NodeValue MathNode::InputValueFromTable(NodeInput *input, NodeValueDatabase &db, bool take) const -{ - if (input == param_a_in_ || input == param_b_in_) { - PairingCalculator calc(db[param_a_in_], db[param_b_in_]); - - NodeValue v = (input == param_a_in_) - ? calc.GetMostLikelyValueA() - : calc.GetMostLikelyValueB(); - - if (take) { - db[input].Remove(v); - } - - return v; - } - - return Node::InputValueFromTable(input, db, take); + return ShaderCode(frag, vert); } NodeValueTable MathNode::Value(NodeValueDatabase &value) const @@ -220,12 +165,8 @@ NodeValueTable MathNode::Value(NodeValueDatabase &value) const // FIXME: Add manual override for this PairingCalculator calc(value[param_a_in_], value[param_b_in_]); - if (!calc.FoundMostLikelyPairing() - || calc.GetMostLikelyPairing() == kPairSampleNumber - || calc.GetMostLikelyPairing() == kPairTextureTexture - || calc.GetMostLikelyPairing() == kPairTextureNumber - || calc.GetMostLikelyPairing() == kPairTextureColor - || calc.GetMostLikelyPairing() == kPairTextureMatrix) { + // Do nothing if no pairing was found + if (!calc.FoundMostLikelyPairing()) { return value.Merge(); } @@ -347,40 +288,82 @@ NodeValueTable MathNode::Value(NodeValueDatabase &value) const break; } - case kPairNone: - case kPairCount: case kPairTextureColor: case kPairTextureNumber: case kPairTextureTexture: case kPairTextureMatrix: + { + ShaderJob job; + + QByteArray shader_id; + QDataStream shader_id_stream(&shader_id, QIODevice::WriteOnly); + shader_id_stream << calc.GetMostLikelyPairing(); + shader_id_stream << val_a.type(); + shader_id_stream << val_b.type(); + + job.SetShaderID(shader_id); + + job.InsertValue(param_a_in_, val_a); + job.InsertValue(param_b_in_, val_b); + + bool operation_is_noop = false; + + if (calc.GetMostLikelyPairing() == kPairTextureNumber) { + NodeValue& number_val = val_a.type() == NodeParam::kTexture ? val_b : val_a; + + if (NumberIsNoOp(GetOperation(), RetrieveNumber(number_val))) { + operation_is_noop = true; + } + } + + if (!operation_is_noop) { + output.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); + } else { + output.Push(val_a.type() == NodeParam::kTexture ? val_a : val_b); + } + break; + } + case kPairSampleNumber: - // Do nothing + { + // Queue a sample job + SampleJob job(val_a.type() == NodeParam::kSamples ? param_a_in_ : param_b_in_); + + 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); + + if (!NumberIsNoOp(GetOperation(), number)) { + job.InsertValue(number_param, NodeValue(NodeParam::kFloat, number, this)); + output.Push(NodeParam::kSampleJob, QVariant::fromValue(job), this); + } else { + output.Push(val_a.type() == NodeParam::kSamples ? val_a : val_b); + } + break; + } + + case kPairNone: + case kPairCount: break; } return output; } -NodeInput *MathNode::ProcessesSamplesFrom(const NodeValueDatabase &value) const +void MathNode::ProcessSamples(NodeValueDatabase &values, const AudioParams ¶ms, const SampleBufferPtr input, SampleBufferPtr output, int index) const { - PairingCalculator calc(value[param_a_in_], value[param_b_in_]); + // This function is only used for sample+number pairing + NodeValue number_val = values[param_a_in_].GetWithMeta(NodeParam::kNumber); - if (calc.GetMostLikelyPairing() == kPairSampleNumber) { - if (calc.GetMostLikelyValueA().type() == NodeParam::kSamples) { - return param_a_in_; - } else { - return param_b_in_; + if (number_val.type() == NodeParam::kNone) { + number_val = values[param_b_in_].GetWithMeta(NodeParam::kNumber); + + if (number_val.type() == NodeParam::kNone) { + return; } } - return nullptr; -} - -void MathNode::ProcessSamples(const NodeValueDatabase &values, const AudioParams ¶ms, const SampleBufferPtr input, SampleBufferPtr output, int index) const -{ - // This function is only used for sample+number pairing - NodeInput* number_input = (ProcessesSamplesFrom(values) == param_a_in_) ? param_b_in_ : param_a_in_; - NodeValue number_val = values[number_input].GetWithMeta(NodeParam::kNumber); float number_flt = RetrieveNumber(number_val); for (int i=0;i pair_likelihood_a = GetPairLikelihood(table_a); @@ -501,8 +505,8 @@ MathNode::PairingCalculator::PairingCalculator(const NodeValueTable &table_a, co } 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_)); + 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_)); } } @@ -513,7 +517,7 @@ QVector MathNode::PairingCalculator::GetPairLikelihood(const NodeValueTable QVector likelihood(kPairCount, -1); for (int i=0;iset_name(tr("Blend")); } -Node::Capabilities MergeNode::GetCapabilities(const NodeValueDatabase &) const +ShaderCode MergeNode::GetShaderCode(const QByteArray &shader_id) const { - return kShader; + Q_UNUSED(shader_id) + + return ShaderCode(ReadFileAsString(":/shaders/alphaover.frag"), QString()); } -QString MergeNode::ShaderFragmentCode(const NodeValueDatabase &) const +NodeValueTable MergeNode::Value(NodeValueDatabase &value) const { - return ReadFileAsString(":/shaders/alphaover.frag"); + ShaderJob job; + job.InsertValue(base_in_, value); + job.InsertValue(blend_in_, value); + + // FIXME: Check if "blend" is RGB-only, in which case it's a no-op + + NodeValueTable table = value.Merge(); + + if (!job.GetValue(base_in_).data().isNull() || !job.GetValue(blend_in_).data().isNull()) { + if (job.GetValue(base_in_).data().isNull()) { + // We only have a blend texture, no need to alpha over + table.Push(job.GetValue(blend_in_)); + } else if (job.GetValue(blend_in_).data().isNull()) { + // We only have a base texture, no need to alpha over + table.Push(job.GetValue(base_in_)); + } else { + // We have both textures, push the job + table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this); + } + } + + return table; } NodeInput *MergeNode::base_in() const diff --git a/app/node/math/merge/merge.h b/app/node/math/merge/merge.h index 63a17cb2d..954c30c85 100644 --- a/app/node/math/merge/merge.h +++ b/app/node/math/merge/merge.h @@ -39,8 +39,8 @@ public: virtual void Retranslate() override; - virtual Capabilities GetCapabilities(const NodeValueDatabase&) const override; - virtual QString ShaderFragmentCode(const NodeValueDatabase&) const override; + virtual ShaderCode GetShaderCode(const QByteArray &shader_id) const override; + virtual NodeValueTable Value(NodeValueDatabase &value) const override; NodeInput* base_in() const; NodeInput* blend_in() const; diff --git a/app/node/node.cpp b/app/node/node.cpp index 9a0b73dff..212a88f04 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -276,11 +276,11 @@ bool Node::HasGizmos() const return false; } -void Node::DrawGizmos(const NodeValueDatabase &, QPainter *, const QVector2D &, const QSize &) const +void Node::DrawGizmos(NodeValueDatabase &, QPainter *, const QVector2D &, const QSize &) const { } -bool Node::GizmoPress(const NodeValueDatabase &, const QPointF &, const QVector2D &, const QSize &viewport) +bool Node::GizmoPress(NodeValueDatabase &, const QPointF &, const QVector2D &, const QSize &) { return false; } @@ -457,42 +457,14 @@ QList Node::GetImmediateDependencies() const return GetDependenciesInternal(false, false); } -Node::Capabilities Node::GetCapabilities(const NodeValueDatabase &) const +ShaderCode Node::GetShaderCode(const QByteArray &shader_id) const { - return kNormal; + Q_UNUSED(shader_id) + + return ShaderCode(QString(), QString()); } -QString Node::ShaderID(const NodeValueDatabase &) const -{ - return id(); -} - -QString Node::ShaderVertexCode(const NodeValueDatabase &) const -{ - return QString(); -} - -QString Node::ShaderFragmentCode(const NodeValueDatabase&) const -{ - return QString(); -} - -int Node::ShaderIterations() const -{ - return 1; -} - -NodeInput *Node::ShaderIterativeInput() const -{ - return nullptr; -} - -NodeInput* Node::ProcessesSamplesFrom(const NodeValueDatabase &) const -{ - return nullptr; -} - -void Node::ProcessSamples(const NodeValueDatabase &, const AudioParams&, const SampleBufferPtr, SampleBufferPtr, int) const +void Node::ProcessSamples(NodeValueDatabase &, const AudioParams&, const SampleBufferPtr, SampleBufferPtr, int) const { } @@ -752,23 +724,6 @@ NodeOutput *Node::output() const return output_; } -NodeValue Node::InputValueFromTable(NodeInput *input, NodeValueDatabase &db, bool take) const -{ - NodeParam::DataType find_data_type = input->data_type(); - - // Exception for Footage types (try to get a Texture instead) - if (find_data_type == NodeParam::kFootage) { - find_data_type = NodeParam::kTexture; - } - - // Try to get a value from it - if (take) { - return db[input].TakeWithMeta(find_data_type); - } else { - return db[input].GetWithMeta(find_data_type); - } -} - const QPointF &Node::GetPosition() const { return position_; diff --git a/app/node/node.h b/app/node/node.h index 068e9c8ec..58dbd44ed 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -35,6 +35,7 @@ #include "node/output.h" #include "node/value.h" #include "render/audioparams.h" +#include "render/shaderinfo.h" OLIVE_NAMESPACE_ENTER @@ -56,12 +57,6 @@ class Node : public QObject { Q_OBJECT public: - enum Capabilities { - kNormal = 0x0, - kShader = 0x1, - kSampleProcessor = 0x2 - }; - enum CategoryID { kCategoryUnknown = -1, @@ -176,47 +171,15 @@ public: */ QList GetImmediateDependencies() const; - /** - * @brief Return accelerated capabilities of this node (if any) - */ - virtual Capabilities GetCapabilities(const NodeValueDatabase&) const; - - /** - * @brief Generate a unique identifier for the shader code (if a node can produce multiple) - */ - virtual QString ShaderID(const NodeValueDatabase&) const; - /** * @brief Generate hardware accelerated code for this Node */ - virtual QString ShaderVertexCode(const NodeValueDatabase&) const; - - /** - * @brief Generate hardware accelerated code for this Node - */ - virtual QString ShaderFragmentCode(const NodeValueDatabase&) const; - - /** - * @brief Number of iterations to run the accelerated code - * - * Some code is faster if it's merely repeated on a resulting texture rather than run once on the same buffer. - */ - virtual int ShaderIterations() const; - - /** - * @brief Parameter that should receive the buffer on an iteration past the first - */ - virtual NodeInput* ShaderIterativeInput() const; - - /** - * @brief Return whether this node processes samples or not - */ - virtual NodeInput* ProcessesSamplesFrom(const NodeValueDatabase &value) const; + virtual ShaderCode GetShaderCode(const QByteArray& shader_id) const; /** * @brief If ProcessesSamples() is true, this is the function that will process them. */ - virtual void ProcessSamples(const NodeValueDatabase &values, const AudioParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const; + virtual void ProcessSamples(NodeValueDatabase &values, const AudioParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const; /** * @brief Returns the input with the specified ID (or nullptr if it doesn't exist) @@ -393,8 +356,6 @@ public: NodeOutput* output() const; - virtual NodeValue InputValueFromTable(NodeInput* input, NodeValueDatabase &db, bool take) const; - const QPointF& GetPosition() const; void SetPosition(const QPointF& pos); @@ -407,9 +368,9 @@ public: virtual bool HasGizmos() const; - virtual void DrawGizmos(const NodeValueDatabase& db, QPainter* p, const QVector2D &scale, const QSize& viewport) const; + virtual void DrawGizmos(NodeValueDatabase& db, QPainter* p, const QVector2D &scale, const QSize& viewport) const; - virtual bool GizmoPress(const NodeValueDatabase& db, const QPointF& p, const QVector2D &scale, const QSize& viewport); + virtual bool GizmoPress(NodeValueDatabase& db, const QPointF& p, const QVector2D &scale, const QSize& viewport); virtual void GizmoMove(const QPointF& p, const QVector2D &scale, const rational &time); virtual void GizmoRelease(); diff --git a/app/node/param.cpp b/app/node/param.cpp index 3cf7a39cb..702702b0a 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -212,6 +212,8 @@ QByteArray NodeParam::ValueToBytes(const NodeParam::DataType &type, const QVaria case kString: case kBuffer: case kVector: + case kShaderJob: + case kSampleJob: case kAny: break; } diff --git a/app/node/param.h b/app/node/param.h index 457667770..76ae4a796 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -178,6 +178,24 @@ public: */ kCombo = 0x8000, + /** + * Job type + * + * An internal type used to indicate to the renderer that an accelerated shader job needs to + * run. This value will usually be taken from a table and a kTexture value will be pushed to + * take its place. + */ + kShaderJob = 0x10000, + + /** + * Job type + * + * An internal type used to indicate to the renderer that an accelerated sample job needs to + * take place. This value will usually be taken from a table and a kSamples value will be + * pushed to take its place. + */ + kSampleJob = 0x20000, + /** ****************************** BROAD IDENTIFIERS ****************************** */ diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index bd182001c..7ab230f32 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -38,9 +38,7 @@ NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRa TimeRange input_time = node->InputTimeAdjustment(input, range); - NodeValueTable table = ProcessInput(input, input_time); - - database.Insert(input, table); + database.Insert(input, ProcessInput(input, input_time)); } // Insert global variables @@ -84,7 +82,7 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang // By this point, the node should have all the inputs it needs to render correctly NodeValueTable table = n->Value(database); - ProcessNodeEvent(n, range, database, table); + PostProcessTable(n, range, table); return table; } @@ -108,9 +106,130 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const TrackOutput *track, const return table; } -StreamPtr NodeTraverser::ResolveStreamFromInput(NodeInput *input) +QVariant NodeTraverser::ProcessVideoFootage(StreamPtr stream, const rational &input_time) { - return input->get_standard_value().value(); + Q_UNUSED(stream) + Q_UNUSED(input_time) + + return QVariant(); +} + +QVariant NodeTraverser::ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) +{ + Q_UNUSED(stream) + Q_UNUSED(input_time) + + return QVariant(); +} + +QVariant NodeTraverser::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) +{ + Q_UNUSED(node) + Q_UNUSED(range) + Q_UNUSED(job) + + return QVariant(); +} + +QVariant NodeTraverser::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) +{ + Q_UNUSED(node) + Q_UNUSED(range) + Q_UNUSED(job) + + return QVariant(); +} + +QVariant NodeTraverser::GetCachedFrame(const Node *node, const rational &time) +{ + Q_UNUSED(node) + Q_UNUSED(time) + + return QVariant(); +} + +void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params) +{ + bool got_cached_frame = false; + + // Convert footage to image/sample buffers + QVariant cached_frame = GetCachedFrame(node, range.in()); + if (!cached_frame.isNull()) { + output_params.Push(NodeParam::kTexture, cached_frame, node); + + // No more to do here + got_cached_frame = true; + } + + // Strip out any jobs or footage + QList video_footage_to_retrieve; + QList audio_footage_to_retrieve; + QList shader_jobs_to_run; + QList sample_jobs_to_run; + + for (int i=output_params.Count()-1; i>=0; i--) { + const NodeValue& v = output_params.at(i); + QList* take_this_value_list = nullptr; + + if (v.type() == NodeParam::kFootage) { + StreamPtr s = v.data().value(); + + if (s) { + if (s->type() == Stream::kVideo + || s->type() == Stream::kImage) { + take_this_value_list = &video_footage_to_retrieve; + } else if (s->type() == Stream::kAudio) { + take_this_value_list = &audio_footage_to_retrieve; + } + } + } else if (v.type() == NodeParam::kShaderJob) { + take_this_value_list = &shader_jobs_to_run; + } else if (v.type() == NodeParam::kSampleJob) { + take_this_value_list = &sample_jobs_to_run; + } + + if (take_this_value_list) { + take_this_value_list->append(output_params.TakeAt(i)); + } + } + + if (!got_cached_frame) { + // Retrieve video frames + foreach (const NodeValue& v, video_footage_to_retrieve) { + QVariant value = ProcessVideoFootage(v.data().value(), range.in()); + + if (!value.isNull()) { + output_params.Push(NodeParam::kTexture, value, node); + } + } + + // Run shaders + foreach (const NodeValue& v, shader_jobs_to_run) { + QVariant value = ProcessShader(node, range, v.data().value()); + + if (!value.isNull()) { + output_params.Push(NodeParam::kTexture, value, node); + } + } + } + + // Retrieve audio samples + foreach (const NodeValue& v, audio_footage_to_retrieve) { + QVariant value = ProcessAudioFootage(v.data().value(), range); + + if (!value.isNull()) { + output_params.Push(NodeParam::kSamples, value, node); + } + } + + // Run any accelerated shader jobs + foreach (const NodeValue& v, sample_jobs_to_run) { + QVariant value = ProcessSamples(node, range, v.data().value()); + + if (!value.isNull()) { + output_params.Push(NodeParam::kSamples, value, node); + } + } } OLIVE_NAMESPACE_EXIT diff --git a/app/node/traverser.h b/app/node/traverser.h index 1ee3dd31f..c7c9651b6 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -39,17 +39,23 @@ public: NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range); - static StreamPtr ResolveStreamFromInput(NodeInput* input); - protected: NodeValueTable ProcessInput(NodeInput *input, const TimeRange &range); virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange& range); - virtual void ProcessNodeEvent(const Node*, - const TimeRange&, - NodeValueDatabase&, - NodeValueTable&) {} + virtual QVariant ProcessVideoFootage(StreamPtr stream, const rational &input_time); + + virtual QVariant ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time); + + virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job); + + virtual QVariant ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job); + + virtual QVariant GetCachedFrame(const Node *node, const rational &time); + +private: + void PostProcessTable(const Node *node, const TimeRange &range, NodeValueTable &output_params); }; diff --git a/app/node/value.cpp b/app/node/value.cpp index fee1a1218..d487f0a3d 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -32,16 +32,6 @@ NodeValueTable& NodeValueDatabase::operator[](const NodeInput *input) return tables_[input->id()]; } -const NodeValueTable NodeValueDatabase::operator[](const QString &input_id) const -{ - return tables_[input_id]; -} - -const NodeValueTable NodeValueDatabase::operator[](const NodeInput *input) const -{ - return tables_[input->id()]; -} - void NodeValueDatabase::Insert(const QString &key, const NodeValueTable &value) { tables_.insert(key, value); @@ -143,7 +133,7 @@ void NodeValueTable::Prepend(const NodeParam::DataType &type, const QVariant &da Prepend(NodeValue(type, data, from, tag)); } -const NodeValue &NodeValueTable::At(int index) const +const NodeValue &NodeValueTable::at(int index) const { return values_.at(index); } @@ -209,7 +199,7 @@ NodeValueTable NodeValueTable::Merge(QList tables) int row_index = t.Count() - 1 - row; - merged_table.Prepend(t.At(row_index)); + merged_table.Prepend(t.at(row_index)); } return merged_table; diff --git a/app/node/value.h b/app/node/value.h index 77b1212c6..387117993 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -60,7 +60,7 @@ public: void Push(const NodeParam::DataType& type, const QVariant& data, const Node *from, const QString& tag = QString()); void Prepend(const NodeValue& value); void Prepend(const NodeParam::DataType& type, const QVariant& data, const Node *from, const QString& tag = QString()); - const NodeValue& At(int index) const; + const NodeValue& at(int index) const; NodeValue TakeAt(int index); int Count() const; bool Has(const NodeParam::DataType& type) const; @@ -85,9 +85,6 @@ public: NodeValueTable& operator[](const QString& input_id); NodeValueTable& operator[](const NodeInput* input); - const NodeValueTable operator[](const QString& input_id) const; - const NodeValueTable operator[](const NodeInput* input) const; - void Insert(const QString& key, const NodeValueTable &value); void Insert(const NodeInput* key, const NodeValueTable& value); @@ -100,6 +97,7 @@ private: OLIVE_NAMESPACE_EXIT +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::NodeValue) Q_DECLARE_METATYPE(OLIVE_NAMESPACE::NodeValueTable) Q_DECLARE_METATYPE(OLIVE_NAMESPACE::NodeValueDatabase) diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 5bcf50787..709a8c5e5 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -40,6 +40,7 @@ set(OLIVE_SOURCES render/playbackcache.h render/playbackcache.cpp render/rendermodes.h + render/shaderinfo.h render/videoparams.h render/videoparams.cpp PARENT_SCOPE diff --git a/app/render/ShaderInfo.h b/app/render/ShaderInfo.h deleted file mode 100644 index a514845ca..000000000 --- a/app/render/ShaderInfo.h +++ /dev/null @@ -1,4 +0,0 @@ -#ifndef SHADERINFO_H -#define SHADERINFO_H - -#endif // SHADERINFO_H diff --git a/app/render/backend/opengl/CMakeLists.txt b/app/render/backend/opengl/CMakeLists.txt index e2972b85f..f1a46cbd7 100644 --- a/app/render/backend/opengl/CMakeLists.txt +++ b/app/render/backend/opengl/CMakeLists.txt @@ -28,7 +28,6 @@ set(OLIVE_SOURCES render/backend/opengl/openglrenderfunctions.cpp render/backend/opengl/openglshader.h render/backend/opengl/openglshader.cpp - render/backend/opengl/openglshadercache.h render/backend/opengl/opengltexture.h render/backend/opengl/opengltexture.cpp render/backend/opengl/opengltexturecache.h diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index 8fe89dfe6..b085826e4 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -142,9 +142,16 @@ QVariant OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, const Video frame_params.divider()); } + PixelFormat::Format texture_fmt; + if (PixelFormat::FormatHasAlphaChannel(frame_params.format())) { + texture_fmt = PixelFormat::GetFormatWithAlphaChannel(params.format()); + } else { + texture_fmt = PixelFormat::GetFormatWithoutAlphaChannel(params.format()); + } + VideoParams dest_params(frame_params.width(), frame_params.height(), - params.format(), + texture_fmt, frame_params.divider()); // Create destination texture @@ -175,6 +182,47 @@ QVariant OpenGLProxy::PreCachedFrameToValue(FramePtr frame) return QVariant::fromValue(texture_cache_.Get(ctx_, frame)); } +OpenGLShaderPtr OpenGLProxy::ResolveShaderFromCache(const Node *node, const QByteArray &shader_id) +{ + // Make a composite of the node ID and the shader ID (if applicable) + QByteArray id = node->id().toUtf8(); + id.append(shader_id); + OpenGLShaderPtr shader = shader_cache_.value(id); + + if (!shader) { + // Since we have shader code, compile it now + ShaderCode code = node->GetShaderCode(shader_id); + QString vert_code = code.vert_code(); + QString frag_code = code.frag_code(); + + if (frag_code.isEmpty() && vert_code.isEmpty()) { + qWarning() << "No shader code found for" << node->id() << "- operation will be a no-op"; + } + + if (frag_code.isEmpty()) { + frag_code = OpenGLShader::CodeDefaultFragment(); + } + + if (vert_code.isEmpty()) { + vert_code = OpenGLShader::CodeDefaultVertex(); + } + + shader = OpenGLShader::Create(); + if (shader + && shader->create() + && shader->addShaderFromSourceCode(QOpenGLShader::Fragment, frag_code) + && shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_code) + && shader->link()) { + shader_cache_.insert(shader_id, shader); + } else { + qWarning() << "Failed to compile shader for" << node->id(); + shader = nullptr; + } + } + + return shader; +} + void OpenGLProxy::Close() { shader_cache_.clear(); @@ -186,185 +234,145 @@ void OpenGLProxy::Close() } QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, - const TimeRange &range, - NodeValueDatabase &input_params, - const VideoParams& params) + const TimeRange &range, + const ShaderJob &job, + const VideoParams& params) { - OpenGLShaderPtr shader = shader_cache_.value(node->ShaderID(input_params)); + // If this node is iterative, we'll pick up which input here + GLuint iterative_input = 0; + QList textures_to_bind; + bool input_textures_have_alpha = false; + + OpenGLShaderPtr shader = ResolveShaderFromCache(node, job.GetShaderID()); if (!shader) { - // Since we have shader code, compile it now - - QString vert_code = node->ShaderVertexCode(input_params); - QString frag_code = node->ShaderFragmentCode(input_params); - - if (frag_code.isEmpty()) { - frag_code = OpenGLShader::CodeDefaultFragment(); - } - - if (vert_code.isEmpty()) { - vert_code = OpenGLShader::CodeDefaultVertex(); - } - - shader = OpenGLShader::Create(); - shader->create(); - shader->addShaderFromSourceCode(QOpenGLShader::Fragment, frag_code); - shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_code); - shader->link(); - - shader_cache_.insert(node->id(), shader); + return QVariant(); } - // Create the output textures - QList dst_refs; - dst_refs.append(texture_cache_.Get(ctx_, params)); - GLuint iterative_input = 0; - - // If this node requires multiple iterations, get a texture for it too - if (node->ShaderIterations() > 1 && node->ShaderIterativeInput()) { - dst_refs.append(texture_cache_.Get(ctx_, params)); - } - - // Lock the shader so no other thread interferes as we set parameters and draw (and we don't interfere with any others) shader->bind(); - unsigned int input_texture_count = 0; + NodeValueMap::const_iterator i; + for (i=job.GetValues().constBegin(); i!=job.GetValues().constEnd(); i++) { + // See if the shader has takes this parameter as an input + int variable_location = shader->uniformLocation(i.key()->id()); - foreach (NodeParam* param, node->parameters()) { - if (param->type() == NodeParam::kInput) { - // See if the shader has takes this parameter as an input - int variable_location = shader->uniformLocation(param->id()); + if (variable_location == -1) { + continue; + } - if (variable_location > -1) { - // This variable is used in the shader, let's set it to our value + // This variable is used in the shader, let's set it + const QVariant& value = i.value().data(); - NodeInput* input = static_cast(param); + const NodeParam::DataType& data_type = (i.value().type() != NodeParam::kNone) + ? i.value().type() + : i.key()->data_type(); - // Get value from database at this input - NodeValue meta_value = node->InputValueFromTable(input, input_params, false); - const QVariant& value = meta_value.data(); + switch (data_type) { + case NodeInput::kInt: + shader->setUniformValue(variable_location, value.toInt()); + break; + case NodeInput::kFloat: + shader->setUniformValue(variable_location, value.toFloat()); + break; + case NodeInput::kVec2: + if (i.key()->IsArray()) { + QVector nv = value.value< QVector >(); + QVector a(nv.size()); - NodeParam::DataType data_type; - - if (meta_value.type() != NodeParam::kNone) { - // Use value's data type - data_type = meta_value.type(); - } else { - // Fallback on null value, send the null to the parameter - data_type = input->data_type(); + for (int j=0;j(); } - switch (data_type) { - case NodeInput::kInt: - shader->setUniformValue(variable_location, value.toInt()); - break; - case NodeInput::kFloat: - shader->setUniformValue(variable_location, value.toFloat()); - break; - case NodeInput::kVec2: - if (input->IsArray()) { - NodeInputArray* array = static_cast(input); - QVector a(array->GetSize()); + shader->setUniformValueArray(variable_location, a.constData(), a.size()); - for (int i=0;iAt(i)].Get(NodeParam::kVec2).value(); - } - - shader->setUniformValueArray(variable_location, a.constData(), a.size()); - - int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(input->id())); - if (count_location > -1) { - shader->setUniformValue(count_location, - array->GetSize()); - } - - } else { - shader->setUniformValue(variable_location, value.value()); - } - break; - case NodeInput::kVec3: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kVec4: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kMatrix: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kCombo: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kColor: - { - Color color = value.value(); - - shader->setUniformValue(variable_location, color.red(), color.green(), color.blue(), color.alpha()); - break; + int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(i.key()->id())); + if (count_location > -1) { + shader->setUniformValue(count_location, a.size()); } - case NodeInput::kBoolean: - shader->setUniformValue(variable_location, value.toBool()); - break; - case NodeInput::kFootage: - case NodeInput::kTexture: - case NodeInput::kBuffer: - { - OpenGLTextureCache::ReferencePtr texture = value.value(); + } else { + shader->setUniformValue(variable_location, value.value()); + } + break; + case NodeInput::kVec3: + shader->setUniformValue(variable_location, value.value()); + break; + case NodeInput::kVec4: + shader->setUniformValue(variable_location, value.value()); + break; + case NodeInput::kMatrix: + shader->setUniformValue(variable_location, value.value()); + break; + case NodeInput::kCombo: + shader->setUniformValue(variable_location, value.value()); + break; + case NodeInput::kColor: + { + Color color = value.value(); - functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count); + shader->setUniformValue(variable_location, color.red(), color.green(), color.blue(), color.alpha()); + break; + } + case NodeInput::kBoolean: + shader->setUniformValue(variable_location, value.toBool()); + break; + case NodeInput::kTexture: + { + OpenGLTextureCache::ReferencePtr texture = value.value(); - GLuint tex_id = texture ? texture->texture()->texture() : 0; - functions_->glBindTexture(GL_TEXTURE_2D, tex_id); - - // Set value to bound texture - shader->setUniformValue(variable_location, input_texture_count); - - // Set enable flag if shader wants it - int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(input->id())); - if (enable_param_location > -1) { - shader->setUniformValue(enable_param_location, - tex_id > 0); - } - - if (tex_id > 0) { - // Set texture resolution if shader wants it - int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(input->id())); - if (res_param_location > -1) { - shader->setUniformValue(res_param_location, - static_cast(texture->texture()->width() * texture->texture()->divider()), - static_cast(texture->texture()->height() * texture->texture()->divider())); - } - } - - // If this texture binding is the iterative input, set it here - if (input == node->ShaderIterativeInput()) { - iterative_input = input_texture_count; - } - - OpenGLRenderFunctions::PrepareToDraw(functions_); - - input_texture_count++; - break; - } - case NodeInput::kSamples: - case NodeInput::kText: - case NodeInput::kRational: - case NodeInput::kFont: - case NodeInput::kFile: - case NodeInput::kDecimal: - case NodeInput::kNumber: - case NodeInput::kString: - case NodeInput::kVector: - case NodeInput::kNone: - case NodeInput::kAny: - break; + if (texture) { + if (PixelFormat::FormatHasAlphaChannel(texture->texture()->format())) { + input_textures_have_alpha = true; } } + + // Set value to bound texture + shader->setUniformValue(variable_location, textures_to_bind.size()); + + // If this texture binding is the iterative input, set it here + if (i.key() == job.GetIterativeInput()) { + iterative_input = textures_to_bind.size(); + } + + GLuint tex_id = texture ? texture->texture()->texture() : 0; + textures_to_bind.append(tex_id); + + // Set enable flag if shader wants it + int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(i.key()->id())); + if (enable_param_location > -1) { + shader->setUniformValue(enable_param_location, + tex_id > 0); + } + + if (tex_id > 0) { + // Set texture resolution if shader wants it + int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(i.key()->id())); + if (res_param_location > -1) { + shader->setUniformValue(res_param_location, + static_cast(texture->texture()->width() * texture->texture()->divider()), + static_cast(texture->texture()->height() * texture->texture()->divider())); + } + } + break; + } + case NodeInput::kSamples: + case NodeInput::kText: + case NodeInput::kRational: + case NodeInput::kFont: + case NodeInput::kFile: + case NodeInput::kDecimal: + case NodeInput::kNumber: + case NodeInput::kString: + case NodeInput::kVector: + case NodeInput::kShaderJob: + case NodeInput::kSampleJob: + case NodeInput::kFootage: + case NodeInput::kBuffer: + case NodeInput::kNone: + case NodeInput::kAny: + break; } } - // Set up OpenGL parameters as necessary - functions_->glViewport(0, 0, params.effective_width(), params.effective_height()); - // Provide some standard args shader->setUniformValue("ove_resolution", static_cast(params.width()), @@ -383,25 +391,65 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, shader->setUniformValue("ove_tprog_in", static_cast(transition_node->GetInProgress(range.in()))); } + shader->release(); + + // Create the output textures + PixelFormat::Format output_format = (input_textures_have_alpha || job.GetAlphaChannelRequired()) + ? PixelFormat::GetFormatWithAlphaChannel(params.format()) + : PixelFormat::GetFormatWithoutAlphaChannel(params.format()); + VideoParams output_params(params.width(), + params.height(), + params.time_base(), + output_format, + params.divider()); + + int real_iteration_count; + if (job.GetIterationCount() > 1 && job.GetIterativeInput()) { + real_iteration_count = job.GetIterationCount(); + } else { + real_iteration_count = 1; + } + + OpenGLTextureCache::ReferencePtr dst_refs[2]; + dst_refs[0] = texture_cache_.Get(ctx_, output_params); + + // If this node requires multiple iterations, get a texture for it too + if (real_iteration_count > 1) { + dst_refs[1] = texture_cache_.Get(ctx_, output_params); + } + // Some nodes use multiple iterations for optimization - OpenGLTextureCache::ReferencePtr output_tex; + OpenGLTextureCache::ReferencePtr input_tex, output_tex; - for (int iteration=0;iterationShaderIterations();iteration++) { - // If this is not the first iteration, set the parameter that will receive the last iteration's texture - OpenGLTextureCache::ReferencePtr source_tex = dst_refs.at((iteration+1)%dst_refs.size()); - OpenGLTextureCache::ReferencePtr destination_tex = dst_refs.at(iteration%dst_refs.size()); + // Set up OpenGL parameters as necessary + functions_->glViewport(0, 0, params.effective_width(), params.effective_height()); + // Bind all textures + for (int i=0; iglActiveTexture(GL_TEXTURE0 + i); + functions_->glBindTexture(GL_TEXTURE_2D, textures_to_bind.at(i)); + OpenGLRenderFunctions::PrepareToDraw(functions_); + } + + for (int iteration=0; iterationbind(); shader->setUniformValue("ove_iteration", iteration); shader->release(); - if (iteration > 0) { + // Replace iterative input + if (iteration == 0) { + output_tex = dst_refs[0]; + } else { + input_tex = dst_refs[(iteration+1)%2]; + output_tex = dst_refs[iteration%2]; + functions_->glActiveTexture(GL_TEXTURE0 + iterative_input); - functions_->glBindTexture(GL_TEXTURE_2D, source_tex->texture()->texture()); + functions_->glBindTexture(GL_TEXTURE_2D, input_tex->texture()->texture()); + OpenGLRenderFunctions::PrepareToDraw(functions_); } - buffer_.Attach(destination_tex->texture(), true); + buffer_.Attach(output_tex->texture()); buffer_.Bind(); // Blit this texture through this shader @@ -409,22 +457,14 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node, buffer_.Release(); buffer_.Detach(); - - // Update output reference to the last texture we wrote to - output_tex = destination_tex; } // Release any textures we bound before - while (input_texture_count > 0) { - input_texture_count--; - - // Release texture here - functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count); + for (int i=textures_to_bind.size()-1; i>=0; i--) { + functions_->glActiveTexture(GL_TEXTURE0 + i); functions_->glBindTexture(GL_TEXTURE_2D, 0); } - shader->release(); - return QVariant::fromValue(output_tex); } diff --git a/app/render/backend/opengl/openglproxy.h b/app/render/backend/opengl/openglproxy.h index ffb1fa56d..9604c4ebd 100644 --- a/app/render/backend/opengl/openglproxy.h +++ b/app/render/backend/opengl/openglproxy.h @@ -28,8 +28,8 @@ #include "node/value.h" #include "openglcolorprocessor.h" #include "openglframebuffer.h" -#include "openglshadercache.h" #include "opengltexturecache.h" +#include "render/shaderinfo.h" OLIVE_NAMESPACE_ENTER @@ -69,7 +69,7 @@ public: public slots: QVariant RunNodeAccelerated(const OLIVE_NAMESPACE::Node *node, const OLIVE_NAMESPACE::TimeRange &range, - OLIVE_NAMESPACE::NodeValueDatabase &input_params, + const OLIVE_NAMESPACE::ShaderJob &job, const OLIVE_NAMESPACE::VideoParams ¶ms); void TextureToBuffer(const QVariant& texture, @@ -84,6 +84,8 @@ public slots: QVariant PreCachedFrameToValue(OLIVE_NAMESPACE::FramePtr frame); private: + OpenGLShaderPtr ResolveShaderFromCache(const Node* node, const QByteArray& shader_id); + QOpenGLContext* ctx_; QOffscreenSurface surface_; @@ -95,7 +97,7 @@ private: OpenGLShaderPtr copy_pipeline_; - OpenGLShaderCache shader_cache_; + QHash shader_cache_; OpenGLTextureCache texture_cache_; diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index f3e66dd9a..b3e10b71c 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -67,7 +67,7 @@ QVariant OpenGLWorker::CachedFrameToTexture(FramePtr frame) const return value; } -QVariant OpenGLWorker::ProcessShader(const Node *node, const TimeRange &range, NodeValueDatabase &input_params) +QVariant OpenGLWorker::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) { QVariant value; @@ -77,10 +77,15 @@ QVariant OpenGLWorker::ProcessShader(const Node *node, const TimeRange &range, N Q_RETURN_ARG(QVariant, value), OLIVE_NS_CONST_ARG(Node*, node), OLIVE_NS_CONST_ARG(TimeRange&, range), - OLIVE_NS_ARG(NodeValueDatabase&, input_params), + OLIVE_NS_CONST_ARG(ShaderJob&, job), OLIVE_NS_CONST_ARG(VideoParams&, video_params())); return value; } +bool OpenGLWorker::TextureHasAlpha(const QVariant &v) const +{ + return PixelFormat::FormatHasAlphaChannel(v.value()->texture()->format()); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h index d190d3abc..23b5e787f 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/backend/opengl/openglworker.h @@ -38,7 +38,9 @@ protected: virtual QVariant CachedFrameToTexture(FramePtr frame) const override; - virtual QVariant ProcessShader(const Node *node, const TimeRange &range, NodeValueDatabase &input_params) override; + virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override; + + virtual bool TextureHasAlpha(const QVariant& v) const override; private: OpenGLProxy* proxy_; diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 24564d790..e5a9756fd 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -47,8 +47,19 @@ void RenderWorker::RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, con QVariant texture = table.Get(NodeParam::kTexture); + PixelFormat::Format output_format; + if (!texture.isNull() && TextureHasAlpha(texture)) { + output_format = PixelFormat::GetFormatWithAlphaChannel(video_params_.format()); + } else { + output_format = PixelFormat::GetFormatWithoutAlphaChannel(video_params_.format()); + } + FramePtr frame = Frame::Create(); - frame->set_video_params(video_params_); + frame->set_video_params(VideoParams(video_params_.width(), + video_params_.height(), + video_params_.time_base(), + output_format, + video_params_.divider())); frame->set_timestamp(time); frame->allocate(); @@ -172,10 +183,11 @@ NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const } } -QVariant RenderWorker::ProcessSamples(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in) +QVariant RenderWorker::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob& job) { - // Copy database so we can make some temporary modifications to it - NodeValueDatabase input_params = input_params_in; + // FIX THIS CODE: + return QVariant(); + /* NodeInput* sample_input = node->ProcessesSamplesFrom(input_params); // Try to find the sample buffer in the table @@ -225,134 +237,20 @@ QVariant RenderWorker::ProcessSamples(const Node *node, const TimeRange &range, } return QVariant::fromValue(output_buffer); + */ } -void RenderWorker::ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in, NodeValueTable &output_params) +QVariant RenderWorker::GetCachedFrame(const Node* node, const rational& time) { - // Convert footage to image/sample buffers if (node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) { - QByteArray hash = RenderBackend::HashNode(node, video_params(), range.in()); + QByteArray hash = RenderBackend::HashNode(node, video_params(), time); QString fn = FrameHashCache::CachePathName(hash); if (QFileInfo::exists(fn)) { FramePtr f = FrameHashCache::LoadCacheFrame(hash); - QVariant cached = CachedFrameToTexture(f); - - if (!cached.isNull()) { - output_params.Push(NodeParam::kTexture, cached, node); - - // No more to do here - return; - } - } - } - - QList inputs = node->GetInputsIncludingArrays(); - foreach (NodeInput* input, inputs) { - if (input->data_type() == NodeParam::kFootage) { - TimeRange input_time = node->InputTimeAdjustment(input, range); - - StreamPtr stream = ResolveStreamFromInput(input); - - if (stream) { - QVariant v = ProcessFootage(stream, input_time); - - if (!v.isNull()) { - if (stream->type() == Stream::kVideo || stream->type() == Stream::kImage) { - output_params.Push(NodeParam::kTexture, v, node); - } else if (stream->type() == Stream::kAudio) { - output_params.Push(NodeParam::kSamples, v, node); - } - } - } - } - } - - // Check if node has a shader - if (node->GetCapabilities(input_params_in) & Node::kShader) { - QVariant v = ProcessShader(node, range, input_params_in); - - if (!v.isNull()) { - output_params.Push(NodeParam::kTexture, v, node); - } - } - - // Check if node processes samples - if (node->GetCapabilities(input_params_in) & Node::kSampleProcessor) { - QVariant v = ProcessSamples(node, range, input_params_in); - - if (!v.isNull()) { - output_params.Push(NodeParam::kSamples, v, node); - } - } -} - -QVariant RenderWorker::GetDataFromStream(StreamPtr stream, const TimeRange &input_time) -{ - DecoderPtr decoder = ResolveDecoderFromInput(stream); - - if (decoder) { - if (stream->type() == Stream::kVideo || stream->type() == Stream::kImage) { - - FramePtr frame = decoder->RetrieveVideo(input_time.in(), - video_params().divider()); - - if (frame) { - // Return a texture from the derived class - return FootageFrameToTexture(stream, frame); - } - - } else if (stream->type() == Stream::kAudio) { - - // See if we have a conformed version of this audio - if (!decoder->HasConformedVersion(audio_params())) { - - // If not, check what audio mode we're in - if (audio_mode_is_preview_) { - - // For preview, we report the conform is missing and finish the render without it - // temporarily. The backend that picks up this signal will recache this section once the - // conform is available. - emit AudioConformUnavailable(decoder->stream(), - audio_render_time_, - input_time.out(), - audio_params()); - - } else { - - // For online rendering/export, it's a waste of time to render the audio until we have - // all we need, so we try to handle the conform ourselves - AudioStreamPtr as = std::static_pointer_cast(stream); - - // Check if any other threads are conforming this audio - if (as->try_start_conforming(audio_params())) { - - // If not, conform it ourselves - decoder->ConformAudio(&IsCancelled(), audio_params()); - - } else { - - // If another thread is conforming already, hackily try to wait until it's done. - do { - QThread::msleep(1000); - } while (!as->has_conformed_version(audio_params()) && !IsCancelled()); - - } - } - - } - - - if (decoder->HasConformedVersion(audio_params())) { - SampleBufferPtr frame = decoder->RetrieveAudio(input_time.in(), input_time.length(), - audio_params()); - - if (frame) { - return QVariant::fromValue(frame); - } - } + return CachedFrameToTexture(f); } } @@ -382,52 +280,117 @@ DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream) return decoder; } -QVariant RenderWorker::ProcessFootage(StreamPtr stream, const TimeRange &input_time) +QVariant RenderWorker::ProcessVideoFootage(StreamPtr stream, const rational &input_time) { - if (stream->type() == Stream::kVideo || stream->type() == Stream::kImage) { + ImageStreamPtr video_stream = std::static_pointer_cast(stream); + rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time; + QString colorspace_match = video_stream->get_colorspace_match_string(); - ImageStreamPtr video_stream = std::static_pointer_cast(stream); - rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time.in(); - QString colorspace_match = video_stream->get_colorspace_match_string(); + QVariant value; + bool found_cache = false; - QVariant value; - bool found_cache = false; + if (still_image_cache_.contains(stream.get())) { + const CachedStill& cs = still_image_cache_[stream.get()]; - if (still_image_cache_.contains(stream.get())) { - const CachedStill& cs = still_image_cache_[stream.get()]; + if (cs.colorspace == colorspace_match + && cs.alpha_is_associated == video_stream->premultiplied_alpha() + && cs.divider == video_params_.divider() + && cs.time == time_match) { + value = cs.texture; + found_cache = true; + } else { + still_image_cache_.remove(stream.get()); + } + } - if (cs.colorspace == colorspace_match - && cs.alpha_is_associated == video_stream->premultiplied_alpha() - && cs.divider == video_params_.divider() - && cs.time == time_match) { - value = cs.texture; - found_cache = true; + if (!found_cache) { + + DecoderPtr decoder = ResolveDecoderFromInput(stream); + + if (decoder) { + FramePtr frame = decoder->RetrieveVideo(input_time, + video_params().divider()); + + if (frame) { + // Return a texture from the derived class + value = FootageFrameToTexture(stream, frame); + + if (value.isNull()) { + qDebug() << "Texture from derivative was blank"; + } else { + // Put this into the image cache instead + still_image_cache_.insert(stream.get(), {value, + colorspace_match, + video_stream->premultiplied_alpha(), + video_params_.divider(), + time_match}); + } } else { - still_image_cache_.remove(stream.get()); + qDebug() << "Frame from decoder was blank"; } } - if (!found_cache) { + } - value = GetDataFromStream(stream, input_time); + return value; +} - still_image_cache_.insert(stream.get(), {value, - colorspace_match, - video_stream->premultiplied_alpha(), - video_params_.divider(), - time_match}); +QVariant RenderWorker::ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) +{ + QVariant value; + + DecoderPtr decoder = ResolveDecoderFromInput(stream); + + if (decoder) { + // See if we have a conformed version of this audio + if (!decoder->HasConformedVersion(audio_params())) { + + // If not, check what audio mode we're in + if (audio_mode_is_preview_) { + + // For preview, we report the conform is missing and finish the render without it + // temporarily. The backend that picks up this signal will recache this section once the + // conform is available. + emit AudioConformUnavailable(decoder->stream(), + audio_render_time_, + input_time.out(), + audio_params()); + + } else { + + // For online rendering/export, it's a waste of time to render the audio until we have + // all we need, so we try to handle the conform ourselves + AudioStreamPtr as = std::static_pointer_cast(stream); + + // Check if any other threads are conforming this audio + if (as->try_start_conforming(audio_params())) { + + // If not, conform it ourselves + decoder->ConformAudio(&IsCancelled(), audio_params()); + + } else { + + // If another thread is conforming already, hackily try to wait until it's done. + do { + QThread::msleep(1000); + } while (!as->has_conformed_version(audio_params()) && !IsCancelled()); + + } + } } - return value; - - } else if (stream->type() == Stream::kAudio) { - - return GetDataFromStream(stream, input_time); + if (decoder->HasConformedVersion(audio_params())) { + SampleBufferPtr frame = decoder->RetrieveAudio(input_time.in(), input_time.length(), + audio_params()); + if (frame) { + value = QVariant::fromValue(frame); + } + } } - return QVariant(); + return value; } OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index fc5f416d0..665f34b42 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -120,9 +120,15 @@ protected: virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange &range) override; - virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in, NodeValueTable &output_params) override; + virtual QVariant ProcessVideoFootage(StreamPtr stream, const rational &input_time) override; - virtual QVariant ProcessShader(const Node *node, const TimeRange &range, NodeValueDatabase &input_params) = 0; + virtual QVariant ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) override; + + virtual QVariant ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) override; + + virtual QVariant GetCachedFrame(const Node *node, const rational &time) override; + + virtual bool TextureHasAlpha(const QVariant& v) const = 0; const VideoParams& video_params() const { @@ -148,14 +154,8 @@ signals: void WaveformGenerated(OLIVE_NAMESPACE::TrackOutput* track, OLIVE_NAMESPACE::AudioVisualWaveform samples, OLIVE_NAMESPACE::TimeRange start); private: - QVariant ProcessSamples(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in); - - QVariant GetDataFromStream(StreamPtr stream, const TimeRange& input_time); - DecoderPtr ResolveDecoderFromInput(StreamPtr stream); - QVariant ProcessFootage(StreamPtr stream, const TimeRange &input_time); - RenderBackend* parent_; VideoParams video_params_; diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index a71c362ad..edd3d4772 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -338,7 +338,9 @@ bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const V header.channels().insert("R", Imf::Channel(pix_type)); header.channels().insert("G", Imf::Channel(pix_type)); header.channels().insert("B", Imf::Channel(pix_type)); - header.channels().insert("A", Imf::Channel(pix_type)); + if (PixelFormat::FormatHasAlphaChannel(vparam.format())) { + header.channels().insert("A", Imf::Channel(pix_type)); + } header.compression() = Imf::DWAA_COMPRESSION; header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f)); @@ -347,14 +349,16 @@ bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const V int bpc = PixelFormat::BytesPerChannel(vparam.format()); - size_t xs = kRGBAChannels * bpc; - size_t ys = vparam.effective_width() * kRGBAChannels * bpc; + size_t xs = PixelFormat::ChannelCount(vparam.format()) * bpc; + size_t ys = vparam.effective_width() * PixelFormat::ChannelCount(vparam.format()) * bpc; Imf::FrameBuffer framebuffer; framebuffer.insert("R", Imf::Slice(pix_type, data, xs, ys)); framebuffer.insert("G", Imf::Slice(pix_type, data + bpc, xs, ys)); framebuffer.insert("B", Imf::Slice(pix_type, data + 2*bpc, xs, ys)); - framebuffer.insert("A", Imf::Slice(pix_type, data + 3*bpc, xs, ys)); + if (PixelFormat::FormatHasAlphaChannel(vparam.format())) { + framebuffer.insert("A", Imf::Slice(pix_type, data + 3*bpc, xs, ys)); + } out.setFrameBuffer(framebuffer); out.writePixels(vparam.effective_height()); diff --git a/app/render/shaderinfo.h b/app/render/shaderinfo.h new file mode 100644 index 000000000..642080ea1 --- /dev/null +++ b/app/render/shaderinfo.h @@ -0,0 +1,157 @@ +#ifndef SHADERINFO_H +#define SHADERINFO_H + +#include "node/input.h" +#include "node/inputarray.h" +#include "node/value.h" + +OLIVE_NAMESPACE_ENTER + +using NodeValueMap = QHash; + +class AcceleratedJob { +public: + AcceleratedJob() = default; + + NodeValue GetValue(NodeInput* input) const + { + return value_map_.value(input); + } + + void InsertValue(NodeInput* input, NodeValueDatabase& value) + { + if (input->IsArray()) { + NodeInputArray* array = static_cast(input); + QVector values(array->GetSize()); + + for (int j=0;jGetSize();j++) { + NodeInput* subparam = array->At(j); + + values[j] = value[subparam].TakeWithMeta(subparam->data_type()); + } + + value_map_.insert(input, NodeValue(NodeParam::kVec2, QVariant::fromValue(values), input->parentNode())); + } else { + value_map_.insert(input, value[input].TakeWithMeta(input->data_type())); + } + } + + void InsertValue(NodeInput* input, const NodeValue& value) + { + value_map_.insert(input, value); + } + + const NodeValueMap &GetValues() const + { + return value_map_; + } + +private: + NodeValueMap value_map_; + +}; + +class SampleJob : public AcceleratedJob { +public: + SampleJob(NodeInput* from = nullptr) : + from_(from) + { + } + + NodeInput* from() const + { + return from_; + } + +private: + NodeInput* from_; + +}; + +class ShaderJob : public AcceleratedJob { +public: + ShaderJob() + { + iterations_ = 1; + iterative_input_ = nullptr; + alpha_channel_required_ = false; + } + + const QByteArray& GetShaderID() const + { + return id_; + } + + void SetShaderID(const QByteArray& id) + { + id_ = id; + } + + void SetIterations(int iterations, NodeInput* iterative_input) + { + iterations_ = iterations; + iterative_input_ = iterative_input; + } + + int GetIterationCount() const + { + return iterations_; + } + + NodeInput* GetIterativeInput() const + { + return iterative_input_; + } + + bool GetAlphaChannelRequired() const + { + return alpha_channel_required_; + } + + void SetAlphaChannelRequired(bool e) + { + alpha_channel_required_ = e; + } + +private: + QByteArray id_; + + int iterations_; + + NodeInput* iterative_input_; + + bool alpha_channel_required_; + +}; + +class ShaderCode { +public: + ShaderCode(const QString& frag_code, const QString& vert_code) : + frag_code_(frag_code), + vert_code_(vert_code) + { + } + + const QString& frag_code() const + { + return frag_code_; + } + + const QString& vert_code() const + { + return vert_code_; + } + +private: + QString frag_code_; + + QString vert_code_; + +}; + +OLIVE_NAMESPACE_EXIT + +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::ShaderJob) +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::SampleJob) + +#endif // SHADERINFO_H diff --git a/app/shaders/blur.frag b/app/shaders/blur.frag index 65092c9cf..4dc2e0566 100644 --- a/app/shaders/blur.frag +++ b/app/shaders/blur.frag @@ -21,6 +21,11 @@ out vec4 fragColor; #define METHOD_BOX_BLUR 0 #define METHOD_GAUSSIAN_BLUR 1 +// Mode +#define MODE_NONE 0 +#define MODE_HORIZONTAL 1 +#define MODE_VERTICAL 2 + // Single gaussian formula (unused, mainly here for documentation/just in case) //float gaussian(float x, float sigma) { // return (1.0/(sigma*sqrt(2.0*M_PI)))*exp(-0.5*pow(x/sigma, 2.0)); @@ -32,10 +37,36 @@ float gaussian2(float x, float y, float sigma) { return (1.0/((sigma*sigma)*2.0*M_PI))*exp(-0.5*(((x*x) + (y*y))/(sigma*sigma))); } +int determine_mode() { + if (radius_in == 0.0) { + return MODE_NONE; + } + + if (!horiz_in && !vert_in) { + return MODE_NONE; + } + + if (horiz_in && !vert_in) { + return MODE_HORIZONTAL; + } + + if (vert_in && !horiz_in) { + return MODE_VERTICAL; + } + + if (ove_iteration == 0) { + return MODE_HORIZONTAL; + } + + if (ove_iteration == 1) { + return MODE_VERTICAL; + } +} + void main(void) { - if (radius_in == 0.0 - || (ove_iteration == 0 && !horiz_in) - || (ove_iteration == 1 && !vert_in)) { + int mode = determine_mode(); + + if (mode == MODE_NONE) { fragColor = texture(tex_in, ove_texcoord); return; } @@ -77,9 +108,9 @@ void main(void) { } vec2 pixel_coord = ove_texcoord; - if (ove_iteration == 0) { + if (mode == MODE_HORIZONTAL) { pixel_coord.x += i / ove_resolution.x; - } else if (ove_iteration == 1) { + } else if (mode == MODE_VERTICAL) { pixel_coord.y += i / ove_resolution.y; } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index da2d636ae..18cd09f16 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -88,6 +88,8 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeParam::kString: case NodeParam::kBuffer: case NodeParam::kVector: + case NodeParam::kShaderJob: + case NodeParam::kSampleJob: break; case NodeParam::kInt: { @@ -262,6 +264,8 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeParam::kString: case NodeParam::kVector: case NodeParam::kBuffer: + case NodeParam::kShaderJob: + case NodeParam::kSampleJob: break; case NodeParam::kInt: { @@ -392,6 +396,8 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() case NodeParam::kNumber: case NodeParam::kString: case NodeParam::kBuffer: + case NodeParam::kShaderJob: + case NodeParam::kSampleJob: case NodeParam::kVector: break; case NodeParam::kInt: diff --git a/app/widget/viewer/gizmotraverser.cpp b/app/widget/viewer/gizmotraverser.cpp index 9cb0f64d0..85ca2fbec 100644 --- a/app/widget/viewer/gizmotraverser.cpp +++ b/app/widget/viewer/gizmotraverser.cpp @@ -22,26 +22,22 @@ OLIVE_NAMESPACE_ENTER -void GizmoTraverser::ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in, NodeValueTable &output_params) +QVariant GizmoTraverser::ProcessVideoFootage(StreamPtr stream, const rational &input_time) { - // Convert footage to image/sample buffers - QList inputs = node->GetInputsIncludingArrays(); - foreach (NodeInput* input, inputs) { - if (input->data_type() == NodeParam::kFootage) { - StreamPtr stream = ResolveStreamFromInput(input); + Q_UNUSED(input_time) - if (stream - && (stream->type() == Stream::kVideo || stream->type() == Stream::kImage)) { - ImageStreamPtr image_stream = std::static_pointer_cast(stream); + ImageStreamPtr image_stream = std::static_pointer_cast(stream); - output_params.Push(NodeParam::kTexture, - QSize(image_stream->width(), image_stream->height()), - node); - } else if (stream->type() == Stream::kAudio) { - // FIXME: Do something... - } - } - } + return QSize(image_stream->width(), image_stream->height()); +} + +QVariant GizmoTraverser::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) +{ + Q_UNUSED(node) + Q_UNUSED(range) + Q_UNUSED(job) + + return size_; } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/gizmotraverser.h b/app/widget/viewer/gizmotraverser.h index b69be083a..af6601882 100644 --- a/app/widget/viewer/gizmotraverser.h +++ b/app/widget/viewer/gizmotraverser.h @@ -28,10 +28,20 @@ OLIVE_NAMESPACE_ENTER class GizmoTraverser : public NodeTraverser { public: - GizmoTraverser() = default; + GizmoTraverser(const QSize& sequence_resolution) : + size_(sequence_resolution) + { + } protected: - virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in, NodeValueTable &output_params) override; + virtual QVariant ProcessVideoFootage(StreamPtr stream, const rational &input_time); + + virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job); + + // FIXME: Do something about audio? + +private: + QSize size_; }; diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 0c51bcfec..420eaf423 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -241,7 +241,7 @@ void ViewerDisplayWidget::paintGL() // Draw gizmos if we have any if (gizmos_) { - GizmoTraverser gt; + GizmoTraverser gt(QSize(gizmo_params_.width(), gizmo_params_.height())); rational node_time = GetGizmoTime();