From 67c7c71ca24b4c7bd56b7f63f2d2160a8617e996 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 17 Sep 2021 20:56:53 -0700 Subject: [PATCH] implemented "value hints" for choosing output values to use --- app/node/block/block.cpp | 2 +- app/node/block/block.h | 2 +- app/node/block/clip/clip.cpp | 2 +- app/node/block/transition/transition.cpp | 6 +- .../transform/transformdistortnode.cpp | 19 ++-- app/node/graph.cpp | 2 + app/node/graph.h | 2 + app/node/math/math/mathbase.cpp | 2 + app/node/math/merge/merge.cpp | 2 +- app/node/node.cpp | 94 +++++++++++++++++++ app/node/node.h | 14 ++- app/node/param.h | 1 + app/node/traverser.cpp | 26 +++-- app/node/traverser.h | 1 + app/node/value.cpp | 6 +- app/node/value.h | 4 +- app/panel/node/node.h | 5 + app/render/previewautocacher.cpp | 36 +++++-- app/render/previewautocacher.h | 8 +- app/render/renderprocessor.cpp | 78 +++++++-------- app/render/renderprocessor.h | 1 + app/widget/CMakeLists.txt | 1 + .../nodeparamviewconnectedlabel.cpp | 49 +++++++++- .../nodeparamviewconnectedlabel.h | 12 +++ .../nodeparamview/nodeparamviewitem.cpp | 4 + app/widget/nodevaluetree/CMakeLists.txt | 22 +++++ app/widget/nodevaluetree/nodevaluetree.cpp | 83 ++++++++++++++++ app/widget/nodevaluetree/nodevaluetree.h | 32 +++++++ app/widget/nodeview/nodeview.h | 5 + app/window/mainwindow/mainwindow.cpp | 3 +- 30 files changed, 439 insertions(+), 85 deletions(-) create mode 100644 app/widget/nodevaluetree/CMakeLists.txt create mode 100644 app/widget/nodevaluetree/nodevaluetree.cpp create mode 100644 app/widget/nodevaluetree/nodevaluetree.h diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index a14fc8cc8..7ba49851f 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -100,7 +100,7 @@ void Block::InputValueChangedEvent(const QString &input, int element) } } -bool Block::HashPassthrough(const QString &input, const Node::ValueHint &output, QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams &video_params) const +bool Block::HashPassthrough(const QString &input, QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams &video_params) const { if (IsInputConnected(input)) { TimeRange t = InputTimeAdjustment(input, -1, globals.time()); diff --git a/app/node/block/block.h b/app/node/block/block.h index e187d3524..4b3fb7f56 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -134,7 +134,7 @@ signals: protected: virtual void InputValueChangedEvent(const QString& input, int element) override; - bool HashPassthrough(const QString &input, const ValueHint &output, QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams& video_params) const; + bool HashPassthrough(const QString &input, QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams& video_params) const; Block* previous_; Block* next_; diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 0d30667a8..d7407a9e1 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -266,7 +266,7 @@ void ClipBlock::Retranslate() void ClipBlock::Hash(const ValueHint &out, QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams &video_params) const { - HashPassthrough(kBufferIn, GetValueHintForInput(kBufferIn, -1), hash, globals, video_params); + HashPassthrough(kBufferIn, hash, globals, video_params); } } diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index d83c80a66..251aa2416 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -139,9 +139,9 @@ double TransitionBlock::GetInProgress(const double &time) const void TransitionBlock::Hash(const ValueHint &output, QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams &video_params) const { - if (HashPassthrough(kInBlockInput, GetValueHintForInput(kInBlockInput, -1), hash, globals, video_params) - || HashPassthrough(kOutBlockInput, GetValueHintForInput(kOutBlockInput, -1), hash, globals, video_params)) { - HashAddNodeSignature(hash, output); + if (HashPassthrough(kInBlockInput, hash, globals, video_params) + || HashPassthrough(kOutBlockInput, hash, globals, video_params)) { + HashAddNodeSignature(hash); double time_dbl = globals.time().in().toDouble(); double all_prog = GetTotalProgress(time_dbl); diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index f6945f001..6791d9350 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -61,17 +61,15 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g // Pop texture NodeValue texture_meta = value[kTextureInput]; - TexturePtr texture = texture_meta.data().value(); + + bool pushed_job = false; // If we have a texture, generate a matrix and make it happen - if (texture) { + if (TexturePtr texture = texture_meta.data().value()) { // Adjust our matrix by the resolutions involved QMatrix4x4 real_matrix = GenerateAutoScaledMatrix(generated_matrix, value, globals, texture->params()); - if (real_matrix.isIdentity()) { - // We don't expect any changes, just push as normal - table->Push(texture_meta); - } else { + if (!real_matrix.isIdentity()) { // The matrix will transform things ShaderJob job; job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture), this)); @@ -83,8 +81,15 @@ void TransformDistortNode::Value(const NodeValueRow &value, const NodeGlobals &g job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); + + pushed_job = true; } } + + if (!pushed_job) { + // Re-push whatever value we received + table->Push(texture_meta); + } } ShaderCode TransformDistortNode::GetShaderCode(const QString &shader_id) const @@ -328,7 +333,7 @@ void TransformDistortNode::Hash(const ValueHint &output, QCryptographicHash &has if (!matrix.isIdentity()) { // Add fingerprint - HashAddNodeSignature(hash, output); + HashAddNodeSignature(hash); hash.addData(reinterpret_cast(&matrix), sizeof(matrix)); } } diff --git a/app/node/graph.cpp b/app/node/graph.cpp index 26bfe6937..53fb04e51 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -98,6 +98,7 @@ void NodeGraph::childEvent(QChildEvent *event) connect(node, &Node::InputConnected, this, &NodeGraph::InputConnected); connect(node, &Node::InputDisconnected, this, &NodeGraph::InputDisconnected); connect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged); + connect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged); emit NodeAdded(node); emit node->AddedToGraph(this); @@ -110,6 +111,7 @@ void NodeGraph::childEvent(QChildEvent *event) disconnect(node, &Node::InputConnected, this, &NodeGraph::InputConnected); disconnect(node, &Node::InputDisconnected, this, &NodeGraph::InputDisconnected); disconnect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged); + disconnect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged); emit NodeRemoved(node); emit node->RemovedFromGraph(this); diff --git a/app/node/graph.h b/app/node/graph.h index 59bcde0f5..5e0aa5be2 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -129,6 +129,8 @@ signals: void ValueChanged(const NodeInput& input); + void InputValueHintChanged(const NodeInput& input); + void NodePositionAdded(Node *node, Node *relative, const QPointF &position); void NodePositionRemoved(Node *node, Node *relative); diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index d76f878aa..f19ede011 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -359,6 +359,8 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt } else { output->Push(NodeValue::kSampleJob, QVariant::fromValue(job), this); } + } else { + output->Push(NodeValue::kSampleJob, QVariant::fromValue(job), this); } break; } diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index 2f14cbed2..b2732e88d 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -116,7 +116,7 @@ void MergeNode::Hash(const ValueHint &output, QCryptographicHash &hash, const No if (!passthrough_base && !passthrough_blend) { // This merge will actually do something so we add a fingerprint - HashAddNodeSignature(hash, output); + HashAddNodeSignature(hash); } if (!passthrough_base) { diff --git a/app/node/node.cpp b/app/node/node.cpp index e0fc5d263..98b326346 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -139,6 +139,28 @@ void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint versi reader->skipCurrentElement(); } } + } else if (reader->name() == QStringLiteral("hints")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("hint")) { + QString input; + int element; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("input")) { + input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("element")) { + element = attr.value().toInt(); + } + } + + ValueHint vh; + vh.Load(reader); + + value_hints_.insert({input, element}, vh); + } else { + reader->skipCurrentElement(); + } + } } else { reader->skipCurrentElement(); } @@ -179,6 +201,19 @@ void Node::Save(QXmlStreamWriter *writer) const } writer->writeEndElement(); // connections + writer->writeStartElement(QStringLiteral("hints")); + for (auto it=value_hints_.cbegin(); it!=value_hints_.cend(); it++) { + writer->writeStartElement(QStringLiteral("hint")); + + writer->writeAttribute(QStringLiteral("input"), it.key().input); + writer->writeAttribute(QStringLiteral("element"), QString::number(it.key().element)); + + it.value().Save(writer); + + writer->writeEndElement(); // hint + } + writer->writeEndElement(); + writer->writeStartElement(QStringLiteral("custom")); SaveCustom(writer); writer->writeEndElement(); // custom @@ -980,6 +1015,15 @@ int Node::InputArraySize(const QString &id) const } } +void Node::SetValueHintForInput(const QString &input, int element, const ValueHint &hint) +{ + value_hints_.insert({input, element}, hint); + + emit InputValueHintChanged(NodeInput(this, input, element)); + + InvalidateAll(input, element); +} + const NodeKeyframeTrack &Node::GetTrackFromKeyframe(NodeKeyframe *key) const { return GetImmediate(key->input(), key->element())->keyframe_tracks().at(key->track()); @@ -1256,6 +1300,7 @@ bool Node::AreLinked(Node *a, Node *b) void Node::HashAddNodeSignature(QCryptographicHash &hash) const { + // Add node ID hash.addData(id().toUtf8()); } @@ -1529,6 +1574,9 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, if (src_element == -1 && dst_element == -1) { dst->ArrayResizeInternal(input, src->InputArraySize(input)); } + + // Copy value hint + dst->SetValueHintForInput(input, dst_element, src->GetValueHintForInput(input, src_element)); } bool Node::CanBeDeleted() const @@ -2408,4 +2456,50 @@ void NodeRemovePositionFromAllContextsCommand::undo() } } +void Node::ValueHint::Hash(QCryptographicHash &hash) const +{ + // Add value hint + foreach (NodeValue::Type t, type) { + hash.addData(reinterpret_cast(&t), sizeof(t)); + } + hash.addData(reinterpret_cast(&index), sizeof(index)); + hash.addData(tag.toUtf8()); +} + +void Node::ValueHint::Load(QXmlStreamReader *reader) +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("types")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("type")) { + type.append(static_cast(reader->readElementText().toInt())); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("index")) { + index = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("tag")) { + tag = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } +} + +void Node::ValueHint::Save(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("types")); + + for (auto it=type.cbegin(); it!=type.cend(); it++) { + writer->writeTextElement(QStringLiteral("type"), QString::number(*it)); + } + + writer->writeEndElement(); // types + + writer->writeTextElement(QStringLiteral("index"), QString::number(index)); + + writer->writeTextElement(QStringLiteral("tag"), tag); +} + } diff --git a/app/node/node.h b/app/node/node.h index 57e7b7045..cbfe75f3b 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -466,6 +466,11 @@ public: QVector type; int index = -1; QString tag; + + void Hash(QCryptographicHash &hash) const; + + void Load(QXmlStreamReader *reader); + void Save(QXmlStreamWriter *writer) const; }; ValueHint GetValueHintForInput(const QString &input, int element) const @@ -473,10 +478,7 @@ public: return value_hints_.value({input, element}); } - void SetValueHintForInput(const QString &input, int element, const ValueHint &hint) - { - value_hints_.insert({input, element}, hint); - } + void SetValueHintForInput(const QString &input, int element, const ValueHint &hint); const NodeKeyframeTrack& GetTrackFromKeyframe(NodeKeyframe* key) const; @@ -965,6 +967,8 @@ signals: void OutputDisconnected(Node *output, const NodeInput& input); + void InputValueHintChanged(const NodeInput& input); + void InputPropertyChanged(const QString& input, const QString& key, const QVariant& value); void LinksChanged(); @@ -1519,4 +1523,6 @@ private: } +Q_DECLARE_METATYPE(olive::Node::ValueHint); + #endif // NODE_H diff --git a/app/node/param.h b/app/node/param.h index 575ef6f8b..63aa413f7 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -221,6 +221,7 @@ uint qHash(const NodeKeyframeTrackReference& i); } +Q_DECLARE_METATYPE(olive::NodeInput) Q_DECLARE_METATYPE(olive::NodeKeyframeTrackReference) #endif // NODEPARAM_H diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 55a786ea7..9619cfc55 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -48,7 +48,8 @@ NodeValueRow NodeTraverser::GenerateRow(NodeValueDatabase *database, const Node NodeValueRow row; for (auto it=database->begin(); it!=database->end(); it++) { // Get hint for which value should be pulled - row.insert(it.key(), GenerateRowValue(node, it.key(), &it.value())); + NodeValue value = GenerateRowValue(node, it.key(), &it.value()); + row.insert(it.key(), value); } return row; @@ -82,6 +83,17 @@ NodeValue NodeTraverser::GenerateRowValue(const Node *node, const QString &input } NodeValue NodeTraverser::GenerateRowValueElement(const Node *node, const QString &input, int element, NodeValueTable *table) +{ + int value_index = GenerateRowValueElementIndex(node, input, element, table); + + if (value_index == -1) { + return NodeValue(); + } else { + return table->TakeAt(value_index); + } +} + +int NodeTraverser::GenerateRowValueElementIndex(const Node *node, const QString &input, int element, const NodeValueTable *table) { Node::ValueHint hint = node->GetValueHintForInput(input, element); QVector types = hint.type; @@ -92,23 +104,23 @@ NodeValue NodeTraverser::GenerateRowValueElement(const Node *node, const QString if (hint.index == -1) { // Get most recent value with this type and tag - return table->TakeWithMeta(types, hint.tag); + return table->GetValueIndex(types, hint.tag); } else { // Try to find value at this index - int index = table->Count() - hint.index; + int index = table->Count() - 1 - hint.index; int diff = 0; while (index + diff < table->Count() && index - diff >= 0) { if (index + diff < table->Count() && types.contains(table->at(index + diff).type())) { - return table->TakeAt(index + diff); + return index + diff; } if (index - diff >= 0 && types.contains(table->at(index - diff).type())) { - return table->TakeAt(index - diff); + return index - diff; } diff++; } - return NodeValue(); + return -1; } } @@ -270,7 +282,7 @@ QVariant NodeTraverser::ProcessSamples(const Node *node, const TimeRange &range, Q_UNUSED(range) Q_UNUSED(job) - return QVariant(); + return QVariant::fromValue(SampleBuffer::Create()); } QVariant NodeTraverser::ProcessFrameGeneration(const Node *node, const GenerateJob &job) diff --git a/app/node/traverser.h b/app/node/traverser.h index 8ad6d822c..14bfed6db 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -45,6 +45,7 @@ public: NodeValue GenerateRowValue(const Node *node, const QString &input, NodeValueTable *table); NodeValue GenerateRowValueElement(const Node *node, const QString &input, int element, NodeValueTable *table); + int GenerateRowValueElementIndex(const Node *node, const QString &input, int element, const NodeValueTable *table); static NodeGlobals GenerateGlobals(const VideoParams ¶ms, const TimeRange &time); static NodeGlobals GenerateGlobals(const VideoParams ¶ms, const rational &time) diff --git a/app/node/value.cpp b/app/node/value.cpp index 8700cf34d..d9b1dfb34 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -326,7 +326,7 @@ QString NodeValue::GetPrettyDataTypeName(Type type) NodeValue NodeValueTable::GetWithMeta(const QVector &type, const QString &tag) const { - int value_index = GetInternal(type, tag); + int value_index = GetValueIndex(type, tag); if (value_index >= 0) { return values_.at(value_index); @@ -337,7 +337,7 @@ NodeValue NodeValueTable::GetWithMeta(const QVector &type, cons NodeValue NodeValueTable::TakeWithMeta(const QVector &type, const QString &tag) { - int value_index = GetInternal(type, tag); + int value_index = GetValueIndex(type, tag); if (value_index >= 0) { return values_.takeAt(value_index); @@ -407,7 +407,7 @@ NodeValueTable NodeValueTable::Merge(QList tables) return merged_table; } -int NodeValueTable::GetInternal(const QVector& types, const QString &tag) const +int NodeValueTable::GetValueIndex(const QVector& types, const QString &tag) const { int index = -1; diff --git a/app/node/value.h b/app/node/value.h index d4ea43f8f..d7018ac20 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -391,11 +391,11 @@ public: return values_.isEmpty(); } + int GetValueIndex(const QVector &type, const QString& tag) const; + static NodeValueTable Merge(QList tables); private: - int GetInternal(const QVector &type, const QString& tag) const; - QVector values_; }; diff --git a/app/panel/node/node.h b/app/panel/node/node.h index 910a48f5f..d145bb91d 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -52,6 +52,11 @@ public: node_view_->ClearGraph(); } + const QVector &GetCurrentContexts() const + { + return node_view_->GetCurrentContexts(); + } + virtual void SelectAll() override { node_view_->SelectAll(); diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index a2a9a50d0..742158d5c 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -14,7 +14,6 @@ namespace olive { PreviewAutoCacher::PreviewAutoCacher() : viewer_node_(nullptr), paused_(false), - has_changed_(false), use_custom_range_(false), single_frame_render_(nullptr) { @@ -141,7 +140,9 @@ void PreviewAutoCacher::HashesProcessed() // The cacher might be waiting for this job to finish if (!graph_update_queue_.isEmpty()) { TryRender(); - } else if (hash_iterator_.HasNext()) { + } + + if (hash_iterator_.HasNext()) { // Launch next hashes QueueNextHashTask(); } @@ -203,7 +204,12 @@ void PreviewAutoCacher::AudioRendered() foreach (TimeRange r, intersections) { // For each range, adjust it relative to the block and write it r -= block->in(); - block->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length()); + + if (waveform_info.silence) { + block->waveform().OverwriteSilence(r.in(), r.length()); + } else { + block->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length()); + } } emit block->PreviewChanged(); @@ -316,6 +322,9 @@ void PreviewAutoCacher::ProcessUpdateQueue() case QueuedJob::kValueChanged: CopyValue(job.input); break; + case QueuedJob::kValueHintChanged: + CopyValueHint(job.input); + break; } } graph_update_queue_.clear(); @@ -379,6 +388,13 @@ void PreviewAutoCacher::CopyValue(const NodeInput &input) Node::CopyValuesOfElement(input.node(), our_input, input.input(), input.element()); } +void PreviewAutoCacher::CopyValueHint(const NodeInput &input) +{ + Node* our_input = copy_map_.value(input.node()); + Node::ValueHint hint = input.node()->GetValueHintForInput(input.input(), input.element()); + our_input->SetValueHintForInput(input.input(), input.element(), hint); +} + void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) { // Insert into map @@ -412,7 +428,6 @@ void PreviewAutoCacher::SetPlayhead(const rational &playhead) cache_range_ = TimeRange(playhead - Config::Current()[QStringLiteral("DiskCacheBehind")].value(), playhead + Config::Current()[QStringLiteral("DiskCacheAhead")].value()); - has_changed_ = true; use_custom_range_ = false; RequeueFrames(); @@ -439,7 +454,6 @@ void PreviewAutoCacher::ClearVideoQueue(bool hard) { ClearQueueInternal(video_tasks_, hard, &PreviewAutoCacher::VideoRendered); - has_changed_ = true; use_custom_range_ = false; queued_frame_iterator_.reset(); } @@ -486,6 +500,12 @@ void PreviewAutoCacher::ValueChanged(const NodeInput &input) UpdateGraphChangeValue(); } +void PreviewAutoCacher::ValueHintChanged(const NodeInput &input) +{ + graph_update_queue_.append({QueuedJob::kValueHintChanged, nullptr, input, nullptr}); + UpdateGraphChangeValue(); +} + void PreviewAutoCacher::TryRender() { if (!graph_update_queue_.isEmpty()) { @@ -564,7 +584,6 @@ void PreviewAutoCacher::RequeueFrames() if (viewer_node_ && viewer_node_->video_frame_cache()->HasInvalidatedRanges(viewer_node_->GetVideoLength()) && hash_tasks_.isEmpty() - && has_changed_ && VideoParams::FormatIsFloat(viewer_node_->GetVideoParams().format()) && (!paused_ || use_custom_range_)) { TimeRange using_range; @@ -580,8 +599,6 @@ void PreviewAutoCacher::RequeueFrames() queued_frame_iterator_ = TimeRangeListFrameIterator(invalidated, viewer_node_->video_frame_cache()->GetTimebase()); QueueNextFrameInRange(RenderManager::GetNumberOfIdealConcurrentJobs()); - - has_changed_ = false; } } @@ -599,7 +616,6 @@ void PreviewAutoCacher::ConformFinished() void PreviewAutoCacher::ForceCacheRange(const TimeRange &range) { - has_changed_ = true; use_custom_range_ = true; custom_autocache_range_ = range; @@ -655,6 +671,7 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) disconnect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded); disconnect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved); disconnect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged); + disconnect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged); // Disconnect signal (will be a no-op if the signal was never connected) disconnect(viewer_node_->video_frame_cache(), @@ -705,6 +722,7 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) connect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded); connect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved); connect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged); + connect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged); // Copy invalidated ranges - used to determine which frames need hashing invalidated_video_ = viewer_node_->video_frame_cache()->GetInvalidatedRanges(viewer_node_->GetVideoLength()); diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index eb6ed5480..ebe12fe7b 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -90,6 +90,7 @@ private: void AddEdge(Node *output, const NodeInput& input); void RemoveEdge(Node *output, const NodeInput& input); void CopyValue(const NodeInput& input); + void CopyValueHint(const NodeInput& input); void InsertIntoCopyMap(Node* node, Node* copy); @@ -124,7 +125,8 @@ private: kNodeRemoved, kEdgeAdded, kEdgeRemoved, - kValueChanged + kValueChanged, + kValueHintChanged }; Type type; @@ -147,8 +149,6 @@ private: TimeRange cache_range_; - bool has_changed_; - bool use_custom_range_; TimeRange custom_autocache_range_; @@ -220,6 +220,8 @@ private slots: void ValueChanged(const NodeInput& input); + void ValueHintChanged(const NodeInput &input); + /** * @brief Generic function called whenever the frames to render need to be (re)queued */ diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 17ab1ef05..7389fb332 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -176,7 +176,7 @@ void RenderProcessor::Run() QVariant sample_variant = table.Get(NodeValue::kSamples); SampleBufferPtr samples = sample_variant.value(); - if (samples && ticket_->property("enablewaveforms").toBool()) { + if (ticket_->property("enablewaveforms").toBool()) { AudioVisualWaveform vis; vis.set_channel_count(samples->audio_params().channel_count()); vis.OverwriteSamples(samples, samples->audio_params().sample_rate()); @@ -264,52 +264,56 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim // Destination buffer NodeValueTable table = GenerateTable(b, track->GetValueHintForInput(Track::kBlockInput, track->GetArrayIndexFromBlock(b)),Track::TransformRangeForBlock(b, range_for_block)); SampleBufferPtr samples_from_this_block = table.Take(NodeValue::kSamples).value(); + ClipBlock *clip_cast = dynamic_cast(b); - if (!samples_from_this_block) { - // If we retrieved no samples from this block, do nothing - continue; + if (samples_from_this_block) { + // If this is a clip, we might have extra speed/reverse information + if (clip_cast) { + double speed_value = clip_cast->speed(); + bool reversed = clip_cast->reverse(); + + if (qIsNull(speed_value)) { + // Just silence, don't think there's any other practical application of 0 speed audio + samples_from_this_block->fill(0); + } else if (!qFuzzyCompare(speed_value, 1.0)) { + // Multiply time + samples_from_this_block->speed(speed_value); + } + + if (reversed) { + samples_from_this_block->reverse(); + } + } + + int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count()); + + // Copy samples into destination buffer + for (int i=0; iaudio_params().channel_count(); i++) { + block_range_buffer->set(i, samples_from_this_block->data(i), destination_offset, copy_length); + } + + NodeValueTable::Merge({merged_table, table}); } - // If this is a clip, we might have extra speed/reverse information - if (ClipBlock *clip_cast = dynamic_cast(b)) { - double speed_value = clip_cast->speed(); - bool reversed = clip_cast->reverse(); + // Create block waveforms if requested + if (ticket_->property("enablewaveforms").toBool() && clip_cast) { + // Format information for use in the main thread + RenderedWaveform waveform_info; + waveform_info.block = clip_cast; + waveform_info.range = range_for_block - b->in(); - if (qIsNull(speed_value)) { - // Just silence, don't think there's any other practical application of 0 speed audio - samples_from_this_block->fill(0); - } else if (!qFuzzyCompare(speed_value, 1.0)) { - // Multiply time - samples_from_this_block->speed(speed_value); - } - - if (reversed) { - samples_from_this_block->reverse(); - } - - // Create block waveforms if requested - if (ticket_->property("enablewaveforms").toBool()) { + if (!(waveform_info.silence = !samples_from_this_block.get())) { // Generate a visual waveform from the samples acquired from this block AudioVisualWaveform visual_waveform; visual_waveform.set_channel_count(audio_params.channel_count()); visual_waveform.OverwriteSamples(samples_from_this_block, audio_params.sample_rate()); - - // Format it for use back int eh maint hread - RenderedWaveform waveform_info = {clip_cast, visual_waveform, range_for_block - b->in()}; - QVector waveform_list = ticket_->property("waveforms").value< QVector >(); - waveform_list.append(waveform_info); - ticket_->setProperty("waveforms", QVariant::fromValue(waveform_list)); + waveform_info.waveform = visual_waveform; } + + QVector waveform_list = ticket_->property("waveforms").value< QVector >(); + waveform_list.append(waveform_info); + ticket_->setProperty("waveforms", QVariant::fromValue(waveform_list)); } - - int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count()); - - // Copy samples into destination buffer - for (int i=0; iaudio_params().channel_count(); i++) { - block_range_buffer->set(i, samples_from_this_block->data(i), destination_offset, copy_length); - } - - NodeValueTable::Merge({merged_table, table}); } } diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index bf2635284..2372623b7 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -39,6 +39,7 @@ public: const ClipBlock* block; AudioVisualWaveform waveform; TimeRange range; + bool silence; }; protected: diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 588ae30ce..38fefccbd 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -33,6 +33,7 @@ add_subdirectory(nodecombobox) add_subdirectory(nodeparamview) add_subdirectory(nodetableview) add_subdirectory(nodetreeview) +add_subdirectory(nodevaluetree) add_subdirectory(nodeview) add_subdirectory(panel) add_subdirectory(path) diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp index 5e0945a5c..faf11e8b7 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp @@ -25,6 +25,7 @@ #include "common/qtutils.h" #include "core.h" #include "node/node.h" +#include "widget/collapsebutton/collapsebutton.h" #include "widget/menu/menu.h" #include "widget/nodeview/nodeviewundo.h" @@ -35,20 +36,29 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, input_(input), connected_node_(nullptr) { - QHBoxLayout* layout = new QHBoxLayout(this); - layout->setSpacing(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" "))); + QVBoxLayout *layout = new QVBoxLayout(this); layout->setMargin(0); - layout->addWidget(new QLabel(tr("Connected to"))); + // Set up label area + QHBoxLayout *label_layout = new QHBoxLayout(); + label_layout->setSpacing(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" "))); + label_layout->setMargin(0); + layout->addLayout(label_layout); + + CollapseButton *collapse_btn = new CollapseButton(); + collapse_btn->setChecked(false); + label_layout->addWidget(collapse_btn); + + label_layout->addWidget(new QLabel(tr("Connected to"))); connected_to_lbl_ = new ClickableLabel(); connected_to_lbl_->setCursor(Qt::PointingHandCursor); connected_to_lbl_->setContextMenuPolicy(Qt::CustomContextMenu); connect(connected_to_lbl_, &ClickableLabel::MouseClicked, this, &NodeParamViewConnectedLabel::ConnectionClicked); connect(connected_to_lbl_, &ClickableLabel::customContextMenuRequested, this, &NodeParamViewConnectedLabel::ShowLabelContextMenu); - layout->addWidget(connected_to_lbl_); + label_layout->addWidget(connected_to_lbl_); - layout->addStretch(); + label_layout->addStretch(); // Set up "link" font QFont link_font = connected_to_lbl_->font(); @@ -64,6 +74,21 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, connect(input_.node(), &Node::InputConnected, this, &NodeParamViewConnectedLabel::InputConnected); connect(input_.node(), &Node::InputDisconnected, this, &NodeParamViewConnectedLabel::InputDisconnected); + + // Set up table area + value_tree_ = new NodeValueTree(); + value_tree_->setVisible(false); + layout->addWidget(value_tree_); + connect(collapse_btn, &CollapseButton::toggled, this, &NodeParamViewConnectedLabel::SetValueTreeVisible); +} + +void NodeParamViewConnectedLabel::SetTime(const rational &time) +{ + time_ = time; + + if (value_tree_->isVisible()) { + UpdateValueTree(); + } } void NodeParamViewConnectedLabel::InputConnected(Node *output, const NodeInput& input) @@ -122,4 +147,18 @@ void NodeParamViewConnectedLabel::UpdateLabel() connected_to_lbl_->setText(s); } +void NodeParamViewConnectedLabel::UpdateValueTree() +{ + value_tree_->SetNode(input_, time_); +} + +void NodeParamViewConnectedLabel::SetValueTreeVisible(bool e) +{ + value_tree_->setVisible(e); + + if (e) { + UpdateValueTree(); + } +} + } diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h index 5f6a2ea06..621a697e1 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h @@ -23,6 +23,7 @@ #include "node/param.h" #include "widget/clickablelabel/clickablelabel.h" +#include "widget/nodevaluetree/nodevaluetree.h" namespace olive { @@ -31,6 +32,8 @@ class NodeParamViewConnectedLabel : public QWidget { public: NodeParamViewConnectedLabel(const NodeInput& input, QWidget* parent = nullptr); + void SetTime(const rational &time); + signals: void RequestSelectNode(const QVector& node); @@ -46,12 +49,21 @@ private slots: private: void UpdateLabel(); + void UpdateValueTree(); + ClickableLabel* connected_to_lbl_; NodeInput input_; Node *connected_node_; + NodeValueTree *value_tree_; + + rational time_; + +private slots: + void SetValueTreeVisible(bool e); + }; } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 9460685f6..fa3c6cd93 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -383,6 +383,10 @@ void NodeParamViewItemBody::SetTime(const rational &time) ui_obj.key_control->SetTime(time); } + if (ui_obj.connected_label) { + ui_obj.connected_label->SetTime(time); + } + ui_obj.widget_bridge->SetTime(time); } } diff --git a/app/widget/nodevaluetree/CMakeLists.txt b/app/widget/nodevaluetree/CMakeLists.txt new file mode 100644 index 000000000..a297a7dd3 --- /dev/null +++ b/app/widget/nodevaluetree/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + widget/nodevaluetree/nodevaluetree.cpp + widget/nodevaluetree/nodevaluetree.h + PARENT_SCOPE +) diff --git a/app/widget/nodevaluetree/nodevaluetree.cpp b/app/widget/nodevaluetree/nodevaluetree.cpp new file mode 100644 index 000000000..f15e5cb92 --- /dev/null +++ b/app/widget/nodevaluetree/nodevaluetree.cpp @@ -0,0 +1,83 @@ +#include "nodevaluetree.h" + +#include + +#include "node/traverser.h" + +namespace olive { + +#define super QTreeWidget + +NodeValueTree::NodeValueTree(QWidget *parent) : + super(parent) +{ + setColumnWidth(0, 0); + setColumnCount(4); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + static const int kMinimumRows = 10; + setMinimumHeight(fontMetrics().height() * kMinimumRows); + + Retranslate(); +} + +void NodeValueTree::SetNode(const NodeInput &input, const rational &time) +{ + clear(); + + NodeTraverser traverser; + + Node *connected_node = input.GetConnectedOutput(); + Node::ValueHint value_hint = input.node()->GetValueHintForInput(input.input(), input.element()); + + NodeValueTable table = traverser.GenerateTable(connected_node, value_hint, TimeRange(time, time)); + + int index = traverser.GenerateRowValueElementIndex(input.node(), input.input(), input.element(), &table); + + for (int i=0; isetProperty("input", QVariant::fromValue(input)); + radio->setProperty("hint", QVariant::fromValue(hint)); + if (i == index) { + radio->setChecked(true); + } + connect(radio, &QRadioButton::clicked, this, &NodeValueTree::RadioButtonChecked); + + setItemWidget(item, 0, radio); + item->setText(1, NodeValue::GetPrettyDataTypeName(value.type())); + item->setText(2, NodeValue::ValueToString(value.type(), value.data(), false)); + item->setText(3, value.source()->GetLabelAndName()); + } +} + +void NodeValueTree::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::LanguageChange) { + Retranslate(); + } + + super::changeEvent(event); +} + +void NodeValueTree::Retranslate() +{ + setHeaderLabels({QString(), tr("Type"), tr("Value"), tr("Source")}); +} + +void NodeValueTree::RadioButtonChecked(bool e) +{ + if (e) { + QRadioButton *btn = static_cast(sender()); + Node::ValueHint hint = btn->property("hint").value(); + NodeInput input = btn->property("input").value(); + + input.node()->SetValueHintForInput(input.input(), input.element(), hint); + } +} + +} diff --git a/app/widget/nodevaluetree/nodevaluetree.h b/app/widget/nodevaluetree/nodevaluetree.h new file mode 100644 index 000000000..94f71ebf1 --- /dev/null +++ b/app/widget/nodevaluetree/nodevaluetree.h @@ -0,0 +1,32 @@ +#ifndef NODEVALUETREE_H +#define NODEVALUETREE_H + +#include +#include + +#include "node/node.h" + +namespace olive { + +class NodeValueTree : public QTreeWidget +{ + Q_OBJECT +public: + NodeValueTree(QWidget *parent = nullptr); + + void SetNode(const NodeInput &input, const rational &time); + +protected: + virtual void changeEvent(QEvent *event) override; + +private: + void Retranslate(); + +private slots: + void RadioButtonChecked(bool e); + +}; + +} + +#endif // NODEVALUETREE_H diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index a3b7dc4fb..db03afc08 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -79,6 +79,11 @@ public: void ZoomOut(); + const QVector &GetCurrentContexts() const + { + return filter_nodes_; + } + public slots: void SetMiniMapEnabled(bool e) { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 612cc68cd..d8fa46c3d 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -741,8 +741,9 @@ void MainWindow::UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel) context.append(viewer); } + QVector old_contexts = node_panel_->GetCurrentContexts(); node_panel_->SetGraph(viewer ? viewer->parent() : nullptr, context); - if (viewer) { + if (viewer && context != old_contexts) { node_panel_->SelectWithDependencies(context, false); } }