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.
This commit is contained in:
@@ -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<rational>();
|
||||
qRegisterMetaType<OpenGLTexturePtr>();
|
||||
qRegisterMetaType<OpenGLTextureCache::ReferencePtr>();
|
||||
qRegisterMetaType<NodeValue>();
|
||||
qRegisterMetaType<NodeValueTable>();
|
||||
qRegisterMetaType<NodeValueDatabase>();
|
||||
qRegisterMetaType<FramePtr>();
|
||||
@@ -553,6 +555,8 @@ void Core::DeclareTypesForQt()
|
||||
qRegisterMetaType<Color>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::ProjectPtr>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::AudioVisualWaveform>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::SampleJob>();
|
||||
qRegisterMetaType<OLIVE_NAMESPACE::ShaderJob>();
|
||||
}
|
||||
|
||||
void Core::StartGUI(bool full_screen)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,9 +37,9 @@ public:
|
||||
virtual QList<CategoryID> 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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,13 +37,16 @@ public:
|
||||
virtual QList<CategoryID> 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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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<QVector2D>());
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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<QPointF> points = GetGizmoCoordinates(db, scale);
|
||||
QVector<QRectF> rects = GetGizmoRects(points);
|
||||
|
||||
@@ -158,7 +171,7 @@ void PolygonGenerator::GizmoRelease()
|
||||
gizmo_y_dragger_.End();
|
||||
}
|
||||
|
||||
QVector<QPointF> PolygonGenerator::GetGizmoCoordinates(const NodeValueDatabase &db, const QVector2D& scale) const
|
||||
QVector<QPointF> PolygonGenerator::GetGizmoCoordinates(NodeValueDatabase &db, const QVector2D& scale) const
|
||||
{
|
||||
QVector<QPointF> points(points_input_->GetSize());
|
||||
|
||||
|
||||
@@ -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<QPointF> GetGizmoCoordinates(const NodeValueDatabase &db, const QVector2D &scale) const;
|
||||
QVector<QPointF> GetGizmoCoordinates(NodeValueDatabase &db, const QVector2D &scale) const;
|
||||
|
||||
QVector<QRectF> GetGizmoRects(const QVector<QPointF>& points) const;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+103
-99
@@ -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<params.channel_count();i++) {
|
||||
@@ -471,6 +454,27 @@ float MathNode::RetrieveNumber(const NodeValue &val)
|
||||
}
|
||||
}
|
||||
|
||||
bool MathNode::NumberIsNoOp(const MathNode::Operation &op, const float &number)
|
||||
{
|
||||
switch (op) {
|
||||
case kOpAdd:
|
||||
case kOpSubtract:
|
||||
if (qIsNull(number)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case kOpMultiply:
|
||||
case kOpDivide:
|
||||
case kOpPower:
|
||||
if (qFuzzyCompare(number, 1.0f)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
MathNode::PairingCalculator::PairingCalculator(const NodeValueTable &table_a, const NodeValueTable &table_b)
|
||||
{
|
||||
QVector<int> 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<int> MathNode::PairingCalculator::GetPairLikelihood(const NodeValueTable
|
||||
QVector<int> likelihood(kPairCount, -1);
|
||||
|
||||
for (int i=0;i<table.Count();i++) {
|
||||
NodeParam::DataType type = table.At(i).type();
|
||||
NodeParam::DataType type = table.at(i).type();
|
||||
|
||||
int weight = i;
|
||||
|
||||
|
||||
@@ -39,17 +39,10 @@ public:
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
virtual Capabilities GetCapabilities(const NodeValueDatabase&) const override;
|
||||
virtual QString ShaderID(const NodeValueDatabase&) const override;
|
||||
virtual QString ShaderFragmentCode(const NodeValueDatabase&) const override;
|
||||
virtual QString ShaderVertexCode(const NodeValueDatabase&input) const override;
|
||||
|
||||
virtual NodeValue InputValueFromTable(NodeInput* input, NodeValueDatabase &db, bool take) const override;
|
||||
|
||||
virtual ShaderCode GetShaderCode(const QByteArray &shader_id) const override;
|
||||
virtual NodeValueTable Value(NodeValueDatabase &value) 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 void ProcessSamples(NodeValueDatabase &values, const AudioParams& params, const SampleBufferPtr input, SampleBufferPtr output, int index) const override;
|
||||
|
||||
NodeInput* param_a_in() const;
|
||||
NodeInput* param_b_in() const;
|
||||
@@ -134,6 +127,8 @@ private:
|
||||
|
||||
static float RetrieveNumber(const NodeValue& val);
|
||||
|
||||
static bool NumberIsNoOp(const Operation& op, const float& number);
|
||||
|
||||
void PushVector(NodeValueTable* output, NodeParam::DataType type, const QVector4D& vec) const;
|
||||
|
||||
NodeInput* method_in_;
|
||||
|
||||
@@ -62,14 +62,37 @@ void MergeNode::Retranslate()
|
||||
blend_in_->set_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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+7
-52
@@ -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 *> 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_;
|
||||
|
||||
+5
-44
@@ -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<Node*> 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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ******************************
|
||||
*/
|
||||
|
||||
+125
-6
@@ -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<StreamPtr>();
|
||||
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<NodeValue> video_footage_to_retrieve;
|
||||
QList<NodeValue> audio_footage_to_retrieve;
|
||||
QList<NodeValue> shader_jobs_to_run;
|
||||
QList<NodeValue> sample_jobs_to_run;
|
||||
|
||||
for (int i=output_params.Count()-1; i>=0; i--) {
|
||||
const NodeValue& v = output_params.at(i);
|
||||
QList<NodeValue>* take_this_value_list = nullptr;
|
||||
|
||||
if (v.type() == NodeParam::kFootage) {
|
||||
StreamPtr s = v.data().value<StreamPtr>();
|
||||
|
||||
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<StreamPtr>(), 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<ShaderJob>());
|
||||
|
||||
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<StreamPtr>(), 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<SampleJob>());
|
||||
|
||||
if (!value.isNull()) {
|
||||
output_params.Push(NodeParam::kSamples, value, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
+12
-6
@@ -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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
+2
-12
@@ -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<NodeValueTable> 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;
|
||||
|
||||
+2
-4
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#ifndef SHADERINFO_H
|
||||
#define SHADERINFO_H
|
||||
|
||||
#endif // SHADERINFO_H
|
||||
@@ -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
|
||||
|
||||
@@ -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<GLuint> 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<OpenGLTextureCache::ReferencePtr> 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<NodeInput*>(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<NodeValue> nv = value.value< QVector<NodeValue> >();
|
||||
QVector<QVector2D> 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<a.size();j++) {
|
||||
a[j] = nv.at(j).data().value<QVector2D>();
|
||||
}
|
||||
|
||||
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<NodeInputArray*>(input);
|
||||
QVector<QVector2D> a(array->GetSize());
|
||||
shader->setUniformValueArray(variable_location, a.constData(), a.size());
|
||||
|
||||
for (int i=0;i<a.size();i++) {
|
||||
a[i] = input_params[array->At(i)].Get(NodeParam::kVec2).value<QVector2D>();
|
||||
}
|
||||
|
||||
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<QVector2D>());
|
||||
}
|
||||
break;
|
||||
case NodeInput::kVec3:
|
||||
shader->setUniformValue(variable_location, value.value<QVector3D>());
|
||||
break;
|
||||
case NodeInput::kVec4:
|
||||
shader->setUniformValue(variable_location, value.value<QVector4D>());
|
||||
break;
|
||||
case NodeInput::kMatrix:
|
||||
shader->setUniformValue(variable_location, value.value<QMatrix4x4>());
|
||||
break;
|
||||
case NodeInput::kCombo:
|
||||
shader->setUniformValue(variable_location, value.value<int>());
|
||||
break;
|
||||
case NodeInput::kColor:
|
||||
{
|
||||
Color color = value.value<Color>();
|
||||
|
||||
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<OpenGLTextureCache::ReferencePtr>();
|
||||
} else {
|
||||
shader->setUniformValue(variable_location, value.value<QVector2D>());
|
||||
}
|
||||
break;
|
||||
case NodeInput::kVec3:
|
||||
shader->setUniformValue(variable_location, value.value<QVector3D>());
|
||||
break;
|
||||
case NodeInput::kVec4:
|
||||
shader->setUniformValue(variable_location, value.value<QVector4D>());
|
||||
break;
|
||||
case NodeInput::kMatrix:
|
||||
shader->setUniformValue(variable_location, value.value<QMatrix4x4>());
|
||||
break;
|
||||
case NodeInput::kCombo:
|
||||
shader->setUniformValue(variable_location, value.value<int>());
|
||||
break;
|
||||
case NodeInput::kColor:
|
||||
{
|
||||
Color color = value.value<Color>();
|
||||
|
||||
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<OpenGLTextureCache::ReferencePtr>();
|
||||
|
||||
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<GLfloat>(texture->texture()->width() * texture->texture()->divider()),
|
||||
static_cast<GLfloat>(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<GLfloat>(texture->texture()->width() * texture->texture()->divider()),
|
||||
static_cast<GLfloat>(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<GLfloat>(params.width()),
|
||||
@@ -383,25 +391,65 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
|
||||
shader->setUniformValue("ove_tprog_in", static_cast<GLfloat>(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;iteration<node->ShaderIterations();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; i<textures_to_bind.size(); i++) {
|
||||
functions_->glActiveTexture(GL_TEXTURE0 + i);
|
||||
functions_->glBindTexture(GL_TEXTURE_2D, textures_to_bind.at(i));
|
||||
OpenGLRenderFunctions::PrepareToDraw(functions_);
|
||||
}
|
||||
|
||||
for (int iteration=0; iteration<real_iteration_count; iteration++) {
|
||||
// Set iteration number
|
||||
shader->bind();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<QByteArray, OpenGLShaderPtr> shader_cache_;
|
||||
|
||||
OpenGLTextureCache texture_cache_;
|
||||
|
||||
|
||||
@@ -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<OpenGLTextureCache::ReferencePtr>()->texture()->format());
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -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_;
|
||||
|
||||
+114
-151
@@ -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<NodeInput*> 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<AudioStream>(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<ImageStream>(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<ImageStream>(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<AudioStream>(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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<NodeInput*, NodeValue>;
|
||||
|
||||
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<NodeInputArray*>(input);
|
||||
QVector<NodeValue> values(array->GetSize());
|
||||
|
||||
for (int j=0;j<array->GetSize();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
|
||||
+36
-5
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<NodeInput*> 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<ImageStream>(stream);
|
||||
ImageStreamPtr image_stream = std::static_pointer_cast<ImageStream>(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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user