From 4dbfbd63ec665f87667ad68e5207eed3b173dbdf Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 18 Sep 2022 22:43:30 -0700 Subject: [PATCH 01/36] nodes: revise array type --- app/node/generator/polygon/polygon.cpp | 23 +++++++++-------- app/node/generator/polygon/polygon.h | 2 +- app/node/node.h | 5 ++++ app/node/traverser.cpp | 35 +++++++++++++++----------- app/node/value.h | 8 ++++-- 5 files changed, 45 insertions(+), 28 deletions(-) diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index ec65f8b7f..0a76dc4fe 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -119,9 +119,9 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con QImage img((uchar *) frame->data(), frame->width(), frame->height(), frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied); img.fill(Qt::transparent); - QVector points = job.Get(kPointsInput).value< QVector >(); + auto points = job.Get(kPointsInput).toArray(); - QPainterPath path = GeneratePath(points); + QPainterPath path = GeneratePath(points, InputArraySize(kPointsInput)); QPainter p(&img); double par = frame->video_params().pixel_aspect_ratio().toDouble(); @@ -171,7 +171,7 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG { QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2); - QVector points = row[kPointsInput].value< QVector >(); + auto points = row[kPointsInput].toArray(); int current_pos_sz = gizmo_position_handles_.size(); @@ -196,8 +196,9 @@ void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeG bez_gizmo2->SetSmaller(true); } - if (!points.isEmpty()) { - for (int i=0; iSetPath(GeneratePath(points).translated(half_res)); + poly_gizmo_->SetPath(GeneratePath(points, pts_sz).translated(half_res)); } ShaderCode PolygonGenerator::GetShaderCode(const ShaderRequest &request) const @@ -246,19 +247,19 @@ void PolygonGenerator::AddPointToPath(QPainterPath *path, const Bezier &before, after.ToPointF()); } -QPainterPath PolygonGenerator::GeneratePath(const QVector &points) +QPainterPath PolygonGenerator::GeneratePath(const NodeValueArray &points, int size) { QPainterPath path; - if (!points.isEmpty()) { - const Bezier &first_pt = points.first().toBezier(); + if (!points.empty()) { + const Bezier &first_pt = points.at(0).toBezier(); path.moveTo(first_pt.ToPointF()); - for (int i=1; i &points); + static QPainterPath GeneratePath(const NodeValueArray &points, int size); template void ValidateGizmoVectorSize(QVector &vec, int new_sz); diff --git a/app/node/node.h b/app/node/node.h index 2ca325d34..5c848100c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -212,6 +212,11 @@ public: return input_ids_; } + virtual bool IsInputActiveAtTime(const QString &input, int element, const TimeRange &r) const + { + return true; + } + bool HasInputWithID(const QString& id) const { return input_ids_.contains(id); diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 766fd1384..712aa798c 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -80,11 +80,11 @@ NodeValue NodeTraverser::GenerateRowValue(const Node *node, const QString &input if (value.array()) { // Resolve each element of array - QVector tables = value.value >(); - QVector output(tables.size()); + NodeValueTableArray tables = value.value(); + NodeValueArray output; - for (int i=0; ifirst] = GenerateRowValueElement(node, input, it->first, &it->second, time); } value = NodeValue(value.type(), QVariant::fromValue(output), value.source(), value.array(), value.tag()); @@ -195,6 +195,10 @@ TexturePtr NodeTraverser::GetMainTextureFromJob(const GenerateJob &job) NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range) { + if (!node->IsInputActiveAtTime(input, -1, range)) { + return NodeValueTable(); + } + // If input is connected, retrieve value directly if (node->IsInputConnected(input)) { @@ -214,18 +218,21 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu if (is_array) { // Value is an array, we will return a list of NodeValueTables - QVector array_tbl(node->InputArraySize(input)); + NodeValueTableArray array_tbl; - for (int i=0; iInputTimeAdjustment(input, i, range); + int sz = node->InputArraySize(input); + for (int i=0; iIsInputActiveAtTime(input, i, range)) { + NodeValueTable& sub_tbl = array_tbl[i]; + TimeRange adjusted_range = node->InputTimeAdjustment(input, i, range); - if (node->IsInputConnected(input, i)) { - Node *output = node->GetConnectedOutput(input, i); - sub_tbl = GenerateTable(output, adjusted_range, node); - } else { - QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), i); - sub_tbl.Push(node->GetInputDataType(input), input_value, node); + if (node->IsInputConnected(input, i)) { + Node *output = node->GetConnectedOutput(input, i); + sub_tbl = GenerateTable(output, adjusted_range, node); + } else { + QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), i); + sub_tbl.Push(node->GetInputDataType(input), input_value, node); + } } } diff --git a/app/node/value.h b/app/node/value.h index e4d373ae7..64bb1300c 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -31,11 +31,15 @@ #include "node/splitvalue.h" #include "render/color.h" #include "render/texture.h" -#include "undo/undocommand.h" namespace olive { class Node; +class NodeValue; +class NodeValueTable; + +using NodeValueArray = std::map; +using NodeValueTableArray = std::map; class NodeValue { @@ -339,7 +343,7 @@ public: QVector3D toVec3() const { return value(); } QVector4D toVec4() const { return value(); } Bezier toBezier() const { return value(); } - QVector toArray() const { return value >(); } + NodeValueArray toArray() const { return value(); } private: Type type_; From 3abc9eb1d1be925a06d1f0445c51041e36e9d5dd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 19 Sep 2022 08:55:37 -0700 Subject: [PATCH 02/36] nodes: revise means of limiting array elements --- app/node/node.h | 33 ++- app/node/output/track/track.cpp | 222 +++++++++++++----- app/node/output/track/track.h | 38 +-- app/node/traverser.cpp | 62 ++--- app/node/traverser.h | 2 +- app/render/renderprocessor.cpp | 106 --------- app/render/renderprocessor.h | 2 - app/widget/timelinewidget/tool/transition.cpp | 2 +- app/widget/viewer/viewerdisplay.cpp | 2 +- 9 files changed, 239 insertions(+), 230 deletions(-) diff --git a/app/node/node.h b/app/node/node.h index 5c848100c..17aefb72a 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -212,9 +212,38 @@ public: return input_ids_; } - virtual bool IsInputActiveAtTime(const QString &input, int element, const TimeRange &r) const + class ActiveElements { - return true; + public: + enum Mode { + kAllElements, + kSpecified, + kNoElements + }; + + ActiveElements(Mode m = kAllElements) + { + mode_ = m; + } + + Mode mode() const { return mode_; } + std::list elements() const { return elements_; } + + void add(int e) + { + elements_.push_back(e); + mode_ = kSpecified; + } + + private: + Mode mode_; + std::list elements_; + + }; + + virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const + { + return ActiveElements::kAllElements; } bool HasInputWithID(const QString& id) const diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index b2d3775f0..b979357d6 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -24,8 +24,10 @@ #include #include +#include "audio/audioprocessor.h" +#include "node/block/clip/clip.h" #include "node/block/gap/gap.h" -#include "node/graph.h" +#include "node/block/transition/transition.h" namespace olive { @@ -95,6 +97,51 @@ QString Track::Description() const "a Sequence."); } +Node::ActiveElements Track::GetActiveElementsAtTime(const QString &input, const TimeRange &r) const +{ + if (input == kBlockInput) { + if (IsMuted() || blocks_.empty() || r.in() >= track_length() || r.out() <= 0) { + return ActiveElements::kNoElements; + } else { + int start = GetBlockIndexAtTime(r.in()); + int end = GetBlockIndexAtTime(r.out()); + + if (start == -1) { + start = 0; + } + if (end == -1) { + end = blocks_.size()-1; + } + + ActiveElements a; + for (int i=start; i<=end; i++) { + Block *b = blocks_.at(i); + if (b->is_enabled()) { + a.add(GetArrayIndexFromCacheIndex(i)); + } + } + + return a; + } + } else { + return super::GetActiveElementsAtTime(input, r); + } +} + +void Track::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + if (this->type() == Track::kVideo) { + // Just pass straight through + NodeValueArray a = value[kBlockInput].toArray(); + if (!a.empty()) { + table->Push(a.begin()->second); + } + } else if (this->type() == Track::kAudio) { + // Audio + ProcessAudioTrack(table, globals.time()); + } +} + TimeRange Track::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const { if (input == kBlockInput && element >= 0) { @@ -352,58 +399,6 @@ Block *Track::NearestBlockAfter(const rational &time) const return nullptr; } -Block *Track::BlockAtTime(const rational &time) const -{ - if (IsMuted() || time > track_length() || blocks_.isEmpty()) { - return nullptr; - } - - // Use binary search to find block at time - Block* using_block = nullptr; - - int low = 0; - int high = blocks_.size() - 1; - while (low <= high) { - int mid = low + (high - low) / 2; - - Block* block = blocks_.at(mid); - if (block->in() <= time && block->out() > time) { - using_block = block; - break; - } else if (block->out() <= time) { - low = mid + 1; - } else { - high = mid - 1; - } - } - - if (using_block && !using_block->is_enabled()) { - using_block = nullptr; - } - - return using_block; -} - -QVector Track::BlocksAtTimeRange(const TimeRange &range) const -{ - QVector list; - - if (IsMuted()) { - return list; - } - - foreach (Block* block, blocks_) { - if (block - && block->is_enabled() - && block->out() > range.in() - && block->in() < range.out()) { - list.append(block); - } - } - - return list; -} - void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) { TimeRange limited; @@ -579,6 +574,127 @@ int Track::GetCacheIndexFromArrayIndex(int index) const return block_array_indexes_.indexOf(index); } +int Track::GetBlockIndexAtTime(const rational &time) const +{ + if (time < 0 || time >= track_length()) { + return -1; + } + + // Use binary search to find block at time + int low = 0; + int high = blocks_.size() - 1; + while (low <= high) { + int mid = low + (high - low) / 2; + + Block* block = blocks_.at(mid); + if (block->in() <= time && block->out() > time) { + return mid; + } else if (block->out() <= time) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return -1; +} + +void Track::ProcessAudioTrack(NodeValueTable *table, const TimeRange &range) const +{ + /* + // All these blocks will need to output to a buffer so we create one here + SampleBuffer block_range_buffer(audio_params, range.length()); + block_range_buffer.silence(); + + NodeValueTable merged_table; + + // Loop through active blocks retrieving their audio + foreach (Block* b, active_blocks) { + if (dynamic_cast(b) || dynamic_cast(b)) { + TimeRange range_for_block(qMax(b->in(), range.in()), + qMin(b->out(), range.out())); + + qint64 destination_offset = audio_params.time_to_samples(range_for_block.in() - range.in()); + qint64 max_dest_sz = audio_params.time_to_samples(range_for_block.length()); + + // Destination buffer + NodeValueTable table = GenerateTable(b, Track::TransformRangeForBlock(b, range_for_block)); + SampleBuffer samples_from_this_block = table.Take(NodeValue::kSamples).toSamples(); + ClipBlock *clip_cast = dynamic_cast(b); + + if (samples_from_this_block.is_allocated()) { + // 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.silence(); + } else if (!qFuzzyCompare(speed_value, 1.0)) { + if (clip_cast->maintain_audio_pitch()) { + AudioProcessor processor; + + if (processor.Open(samples_from_this_block.audio_params(), samples_from_this_block.audio_params(), speed_value)) { + AudioProcessor::Buffer out; + + // FIXME: This is not the best way to do this, the TempoProcessor works best + // when it's given a continuous stream of audio, which is challenging + // in our current "modular" audio system. This should still work reasonably + // well on export (assuming audio is all generated at once on export), but + // users may hear clicks and pops in the audio during preview due to this + // approach. + int r = processor.Convert(samples_from_this_block.to_raw_ptrs().data(), samples_from_this_block.sample_count(), nullptr); + + if (r < 0) { + qCritical() << "Failed to change tempo of audio:" << r; + } else { + processor.Flush(); + + processor.Convert(nullptr, 0, &out); + + if (!out.empty()) { + int nb_samples = out.front().size() * samples_from_this_block.audio_params().bytes_per_sample_per_channel(); + + if (nb_samples) { + SampleBuffer new_samples(samples_from_this_block.audio_params(), nb_samples); + + for (int i=0; iPush(NodeValue::kSamples, QVariant::fromValue(block_range_buffer), this); + */ +} + void Track::BlockLengthChanged() { // Assumes sender is a Block diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 4994bf73c..e2ac8d3bb 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -22,7 +22,6 @@ #define TRACK_H #include "node/block/block.h" -#include "timeline/timelinecommon.h" namespace olive { @@ -55,6 +54,9 @@ public: virtual QVector Category() const override; virtual QString Description() const override; + virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; virtual TimeRange OutputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; @@ -314,30 +316,6 @@ public: */ Block* NearestBlockAfter(const rational& time) const; - /** - * @brief Returns the block that should be rendered/visible at a given time - * - * Use this for any video rendering or determining which block will actually be active at any - * time. - * - * @return Catches the first block that matches `block.in <= time && block.out > time`. Returns - * nullptr if the time exceeds the track length, the block active at this time is disabled, or - * if IsMuted() is true. - */ - Block* BlockAtTime(const rational& time) const; - - /** - * @brief Returns a list of blocks that should be rendered/visible during a given time range - * - * Use this for audio rendering to determine all blocks that will be active throughout a range - * of time. - * - * @return Similar to BlockAtTime() but will match several blocks where - * `block.in < range.out && block.out > range.in`. Returns an empty list if IsMuted() or if - * `range.in >= track.length`. Blocks that are not enabled will be omitted from the returned list. - */ - QVector BlocksAtTimeRange(const TimeRange& range) const; - const QVector &Blocks() const { return blocks_; @@ -345,6 +323,12 @@ public: virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override; + Block *VisibleBlockAtTime(const rational &t) const + { + int index = GetBlockIndexAtTime(t); + return (index == -1) ? nullptr : blocks_.at(index); + } + /** * @brief Adds Block `block` at the very beginning of the Sequence before all other clips */ @@ -467,6 +451,10 @@ private: int GetCacheIndexFromArrayIndex(int index) const; + int GetBlockIndexAtTime(const rational &time) const; + + void ProcessAudioTrack(NodeValueTable *table, const TimeRange &range) const; + TimeRangeList block_length_pending_invalidations_; QVector blocks_; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 712aa798c..8fab59bb9 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -195,10 +195,6 @@ TexturePtr NodeTraverser::GetMainTextureFromJob(const GenerateJob &job) NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range) { - if (!node->IsInputActiveAtTime(input, -1, range)) { - return NodeValueTable(); - } - // If input is connected, retrieve value directly if (node->IsInputConnected(input)) { @@ -220,19 +216,15 @@ NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& inpu // Value is an array, we will return a list of NodeValueTables NodeValueTableArray array_tbl; - int sz = node->InputArraySize(input); - for (int i=0; iIsInputActiveAtTime(input, i, range)) { - NodeValueTable& sub_tbl = array_tbl[i]; - TimeRange adjusted_range = node->InputTimeAdjustment(input, i, range); - - if (node->IsInputConnected(input, i)) { - Node *output = node->GetConnectedOutput(input, i); - sub_tbl = GenerateTable(output, adjusted_range, node); - } else { - QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), i); - sub_tbl.Push(node->GetInputDataType(input), input_value, node); - } + Node::ActiveElements a = node->GetActiveElementsAtTime(input, range); + if (a.mode() == Node::ActiveElements::kAllElements) { + int sz = node->InputArraySize(input); + for (int i=0; iInputTimeAdjustment(input, element, range); + + if (node->IsInputConnected(input, element)) { + Node *output = node->GetConnectedOutput(input, element); + sub_tbl = GenerateTable(output, adjusted_range, node); + } else { + QVariant input_value = node->GetValueAtTime(input, adjusted_range.in(), element); + sub_tbl.Push(node->GetInputDataType(input), input_value, node); + } +} + NodeTraverser::NodeTraverser() : cancel_(nullptr), transform_(nullptr), @@ -278,12 +284,6 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang // NOTE: Times how long a node takes to process, useful for profiling. //GTTTime gtt(n);Q_UNUSED(gtt); - const Track* track = dynamic_cast(n); - if (track) { - // If the range is not wholly contained in this Block, we'll need to do some extra processing - return GenerateBlockTable(track, range); - } - // FIXME: Cache certain values here if we've already processed them before // Generate row for node @@ -338,22 +338,6 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang } } -NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeRange &range) -{ - // By default, just follow the in point - Block* active_block = track->BlockAtTime(range.in()); - - NodeValueTable table; - - if (active_block) { - block_stack_.push_back(active_block); - table = GenerateTable(active_block, Track::TransformRangeForBlock(active_block, range), track); - block_stack_.pop_back(); - } - - return table; -} - TexturePtr NodeTraverser::ProcessVideoCacheJob(const CacheJob &val) { return nullptr; diff --git a/app/node/traverser.h b/app/node/traverser.h index bde1733ae..26c9afbcf 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -87,7 +87,7 @@ public: protected: NodeValueTable ProcessInput(const Node *node, const QString &input, const TimeRange &range); - virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange& range); + void ProcessInputElement(NodeValueTableArray &array_tbl, const Node *node, const QString &input, int element, const TimeRange &range); virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time){} diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 426b0a9ed..84c4ec67f 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -300,112 +300,6 @@ void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, Deco p.Run(); } -NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const TimeRange &range) -{ - if (track->type() == Track::kAudio) { - - const AudioParams& audio_params = GetCacheAudioParams(); - - QVector active_blocks = track->BlocksAtTimeRange(range); - - // All these blocks will need to output to a buffer so we create one here - SampleBuffer block_range_buffer(audio_params, range.length()); - block_range_buffer.silence(); - - NodeValueTable merged_table; - - // Loop through active blocks retrieving their audio - foreach (Block* b, active_blocks) { - if (dynamic_cast(b) || dynamic_cast(b)) { - TimeRange range_for_block(qMax(b->in(), range.in()), - qMin(b->out(), range.out())); - - qint64 destination_offset = audio_params.time_to_samples(range_for_block.in() - range.in()); - qint64 max_dest_sz = audio_params.time_to_samples(range_for_block.length()); - - // Destination buffer - NodeValueTable table = GenerateTable(b, Track::TransformRangeForBlock(b, range_for_block)); - SampleBuffer samples_from_this_block = table.Take(NodeValue::kSamples).toSamples(); - ClipBlock *clip_cast = dynamic_cast(b); - - if (samples_from_this_block.is_allocated()) { - // 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.silence(); - } else if (!qFuzzyCompare(speed_value, 1.0)) { - if (clip_cast->maintain_audio_pitch()) { - AudioProcessor processor; - - if (processor.Open(samples_from_this_block.audio_params(), samples_from_this_block.audio_params(), speed_value)) { - AudioProcessor::Buffer out; - - // FIXME: This is not the best way to do this, the TempoProcessor works best - // when it's given a continuous stream of audio, which is challenging - // in our current "modular" audio system. This should still work reasonably - // well on export (assuming audio is all generated at once on export), but - // users may hear clicks and pops in the audio during preview due to this - // approach. - int r = processor.Convert(samples_from_this_block.to_raw_ptrs().data(), samples_from_this_block.sample_count(), nullptr); - - if (r < 0) { - qCritical() << "Failed to change tempo of audio:" << r; - } else { - processor.Flush(); - - processor.Convert(nullptr, 0, &out); - - if (!out.empty()) { - int nb_samples = out.front().size() * samples_from_this_block.audio_params().bytes_per_sample_per_channel(); - - if (nb_samples) { - SampleBuffer new_samples(samples_from_this_block.audio_params(), nb_samples); - - for (int i=0; iproperty("type").value() != RenderManager::kTypeVideo) { diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index a537ccf89..6dc02f04c 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -42,8 +42,6 @@ public: }; protected: - virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange &range) override; - virtual void ProcessVideoFootage(TexturePtr destination, const FootageJob &stream, const rational &input_time) override; virtual void ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time) override; diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index f03f06297..3146142a8 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -178,7 +178,7 @@ bool TransitionTool::GetBlocksAtCoord(const TimelineCoordinate &coord, ClipBlock return false; } - Block* block_at_time = t->BlockAtTime(coord.GetFrame()); + Block* block_at_time = t->NearestBlockBeforeOrAt(coord.GetFrame()); if (!dynamic_cast(block_at_time)) { return false; } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 70bbc51f9..23d41efb1 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -1056,7 +1056,7 @@ void ViewerDisplayWidget::DrawSubtitleTracks() for (int j=subtitle_tracklist.size()-1; j>=0; j--) { Track *sub_track = subtitle_tracklist.at(j); if (!sub_track->IsMuted()) { - if (SubtitleBlock *sub = dynamic_cast(sub_track->BlockAtTime(time_))) { + if (SubtitleBlock *sub = dynamic_cast(sub_track->VisibleBlockAtTime(time_))) { // Split into lines QStringList list = QtUtils::WordWrapString(sub->GetText(), fm, bounding_box.width()); From 2dc2edf3e072ecef8a95a59ebb60d29c005b134d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 19 Sep 2022 08:55:56 -0700 Subject: [PATCH 03/36] multicam: initial implementation --- app/node/factory.cpp | 3 ++ app/node/factory.h | 1 + app/node/input/CMakeLists.txt | 1 + app/node/input/multicam/CMakeLists.txt | 22 +++++++++ app/node/input/multicam/multicamnode.cpp | 58 ++++++++++++++++++++++++ app/node/input/multicam/multicamnode.h | 25 +++++++++- 6 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 app/node/input/multicam/CMakeLists.txt diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 41d6f55a5..2ac7bc99d 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -49,6 +49,7 @@ #include "generator/text/textv1.h" #include "generator/text/textv2.h" #include "generator/text/textv3.h" +#include "input/multicam/multicamnode.h" #include "input/time/timeinput.h" #include "input/value/valuenode.h" #include "keying/chromakey/chromakey.h" @@ -294,6 +295,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new DropShadowFilter(); case kTimeFormat: return new TimeFormatNode(); + case kMulticamNode: + return new MultiCamNode(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index b6f6037f0..4cad9b984 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -76,6 +76,7 @@ public: kMaskDistort, kDropShadowFilter, kTimeFormat, + kMulticamNode, // Count value kInternalNodeCount diff --git a/app/node/input/CMakeLists.txt b/app/node/input/CMakeLists.txt index 6de136173..ab2b3569e 100644 --- a/app/node/input/CMakeLists.txt +++ b/app/node/input/CMakeLists.txt @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +add_subdirectory(multicam) add_subdirectory(time) add_subdirectory(value) diff --git a/app/node/input/multicam/CMakeLists.txt b/app/node/input/multicam/CMakeLists.txt new file mode 100644 index 000000000..fca12be16 --- /dev/null +++ b/app/node/input/multicam/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/input/multicam/multicamnode.h + node/input/multicam/multicamnode.cpp + PARENT_SCOPE +) diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index 0c1273cb5..827d035d8 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -1,6 +1,64 @@ #include "multicamnode.h" +namespace olive { + +#define super Node + +const QString MultiCamNode::kCurrentInput = QStringLiteral("current_in"); +const QString MultiCamNode::kSourcesInput = QStringLiteral("sources_in"); + MultiCamNode::MultiCamNode() { + AddInput(kCurrentInput, NodeValue::kInt, InputFlags(kInputFlagStatic)); + + AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); +} + +QString MultiCamNode::Name() const +{ + return tr("Multi-Cam"); +} + +QString MultiCamNode::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.multicam"); +} + +QVector MultiCamNode::Category() const +{ + return {kCategoryTimeline}; +} + +QString MultiCamNode::Description() const +{ + return tr("Allows easy switching between multiple sources."); +} + +Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, const TimeRange &r) const +{ + if (input == kSourcesInput) { + Node::ActiveElements a; + a.add(GetStandardValue(kCurrentInput).toInt()); + return a; + } else { + return super::GetActiveElementsAtTime(input, r); + } +} + +void MultiCamNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + NodeValueArray arr = value[kSourcesInput].toArray(); + if (!arr.empty()) { + table->Push(arr.begin()->second); + } +} + +void MultiCamNode::Retranslate() +{ + super::Retranslate(); + + SetInputName(kCurrentInput, tr("Current")); + SetInputName(kSourcesInput, tr("Sources")); +} } diff --git a/app/node/input/multicam/multicamnode.h b/app/node/input/multicam/multicamnode.h index c21e7f6ca..d8895a7f5 100644 --- a/app/node/input/multicam/multicamnode.h +++ b/app/node/input/multicam/multicamnode.h @@ -1,11 +1,34 @@ #ifndef MULTICAMNODE_H #define MULTICAMNODE_H +#include "node/node.h" -class MultiCamNode +namespace olive { + +class MultiCamNode : public Node { + Q_OBJECT public: MultiCamNode(); + + NODE_DEFAULT_FUNCTIONS(MultiCamNode) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override; + + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + virtual void Retranslate() override; + + static const QString kCurrentInput; + static const QString kSourcesInput; + }; +} + #endif // MULTICAMNODE_H From 487cd166d7ab6626ba6e6d35a313da1ae5faa8bb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 21 Sep 2022 13:16:19 -0700 Subject: [PATCH 04/36] start implementation of multicam panel --- app/panel/CMakeLists.txt | 1 + app/panel/multicam/CMakeLists.txt | 22 ++++ app/panel/multicam/multicampanel.cpp | 26 +++++ app/panel/multicam/multicampanel.h | 37 +++++++ app/panel/timebased/timebased.h | 7 +- app/render/previewautocacher.cpp | 24 ++++- app/render/previewautocacher.h | 1 + app/shaders/multicam.frag | 7 ++ app/widget/CMakeLists.txt | 1 + app/widget/manageddisplay/manageddisplay.cpp | 8 ++ app/widget/manageddisplay/manageddisplay.h | 5 +- app/widget/multicam/CMakeLists.txt | 22 ++++ app/widget/multicam/multicamwidget.cpp | 104 +++++++++++++++++++ app/widget/multicam/multicamwidget.h | 59 +++++++++++ app/widget/scope/histogram/histogram.cpp | 4 +- app/widget/scope/scopebase/scopebase.cpp | 8 +- app/widget/scope/waveform/waveform.cpp | 4 +- app/widget/viewer/viewer.h | 2 + app/widget/viewer/viewerdisplay.cpp | 5 +- app/window/mainwindow/mainwindow.cpp | 23 ++++ app/window/mainwindow/mainwindow.h | 2 + 21 files changed, 347 insertions(+), 25 deletions(-) create mode 100644 app/panel/multicam/CMakeLists.txt create mode 100644 app/panel/multicam/multicampanel.cpp create mode 100644 app/panel/multicam/multicampanel.h create mode 100644 app/shaders/multicam.frag create mode 100644 app/widget/multicam/CMakeLists.txt create mode 100644 app/widget/multicam/multicamwidget.cpp create mode 100644 app/widget/multicam/multicamwidget.h diff --git a/app/panel/CMakeLists.txt b/app/panel/CMakeLists.txt index e13af427d..6bfa5a43b 100644 --- a/app/panel/CMakeLists.txt +++ b/app/panel/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory(audiomonitor) add_subdirectory(curve) add_subdirectory(footageviewer) +add_subdirectory(multicam) add_subdirectory(node) add_subdirectory(param) add_subdirectory(pixelsampler) diff --git a/app/panel/multicam/CMakeLists.txt b/app/panel/multicam/CMakeLists.txt new file mode 100644 index 000000000..89062f21e --- /dev/null +++ b/app/panel/multicam/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + panel/multicam/multicampanel.h + panel/multicam/multicampanel.cpp + PARENT_SCOPE +) diff --git a/app/panel/multicam/multicampanel.cpp b/app/panel/multicam/multicampanel.cpp new file mode 100644 index 000000000..97245437a --- /dev/null +++ b/app/panel/multicam/multicampanel.cpp @@ -0,0 +1,26 @@ +#include "multicampanel.h" + +namespace olive { + +#define super PanelWidget + +MulticamPanel::MulticamPanel(ViewerPanelBase *sibling, QWidget *parent) : + super(QStringLiteral("MultiCamPanel"), parent) +{ + widget_ = new MulticamWidget(); + SetWidgetWithPadding(widget_); + + connect(sibling, &ViewerPanelBase::ColorManagerChanged, widget_, &MulticamWidget::ConnectColorManager); + widget_->ConnectCacher(static_cast(sibling->GetTimeBasedWidget())->GetCacher()); + + Retranslate(); +} + +void MulticamPanel::Retranslate() +{ + super::Retranslate(); + + SetTitle(tr("Multi-Cam")); +} + +} diff --git a/app/panel/multicam/multicampanel.h b/app/panel/multicam/multicampanel.h new file mode 100644 index 000000000..61a8efed7 --- /dev/null +++ b/app/panel/multicam/multicampanel.h @@ -0,0 +1,37 @@ +#ifndef MULTICAMPANEL_H +#define MULTICAMPANEL_H + +#include "panel/viewer/viewerbase.h" +#include "widget/multicam/multicamwidget.h" +#include "widget/panel/panel.h" + +namespace olive { + +class MulticamPanel : public PanelWidget +{ + Q_OBJECT +public: + MulticamPanel(ViewerPanelBase *sibling, QWidget* parent = nullptr); + + void SetNode(MultiCamNode *n) + { + widget_->SetNode(n); + } + +public slots: + void SetTime(const rational &t) + { + widget_->SetTime(t); + } + +protected: + virtual void Retranslate() override; + +private: + MulticamWidget *widget_; + +}; + +} + +#endif // MULTICAMPANEL_H diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h index dff33ccca..4101e6b09 100644 --- a/app/panel/timebased/timebased.h +++ b/app/panel/timebased/timebased.h @@ -106,6 +106,8 @@ public: virtual void Paste() override; + TimeBasedWidget* GetTimeBasedWidget() const { return widget_; } + public slots: void SetTimebase(const rational& timebase); @@ -127,11 +129,6 @@ signals: void ShuttleRightRequested(); protected: - TimeBasedWidget* GetTimeBasedWidget() const - { - return widget_; - } - void SetTimeBasedWidget(TimeBasedWidget* widget); virtual void Retranslate() override; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 5a9a5a21e..9a0de0898 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -62,6 +62,11 @@ PreviewAutoCacher::~PreviewAutoCacher() } RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry) +{ + return GetSingleFrame(viewer_node_->GetConnectedTextureOutput(), t, dry); +} + +RenderTicketPtr PreviewAutoCacher::GetSingleFrame(Node *n, const rational &t, bool dry) { // If we have a single frame render queued (but not yet sent to the RenderManager), cancel it now CancelQueuedSingleFrameRender(); @@ -71,6 +76,7 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool dry) sfr->Start(); sfr->setProperty("time", QVariant::fromValue(t)); sfr->setProperty("dry", dry); + sfr->setProperty("node", Node::PtrToValue(n)); // Queue it and try to render single_frame_render_ = sfr; @@ -611,11 +617,19 @@ void PreviewAutoCacher::TryRender() single_frame_render_ = nullptr; // Check if already caching this - RenderTicketWatcher *watcher = RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), - t->property("time").value(), - nullptr, - t->property("dry").toBool()); - video_immediate_passthroughs_[watcher].append(t); + Node *n = Node::ValueToPtr(t->property("node")); + Node *copy = copy_map_.value(n); + + if (copy) { + RenderTicketWatcher *watcher = RenderFrame(copy, + t->property("time").value(), + nullptr, + t->property("dry").toBool()); + video_immediate_passthroughs_[watcher].append(t); + } else { + qWarning() << "Failed to find copied node for SFR ticket"; + t->Finish(); + } } if (!pause_renders_) { diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 05e253851..6f0fc6ac7 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -50,6 +50,7 @@ public: virtual ~PreviewAutoCacher() override; RenderTicketPtr GetSingleFrame(const rational& t, bool dry = false); + RenderTicketPtr GetSingleFrame(Node *n, const rational& t, bool dry = false); RenderTicketPtr GetRangeOfAudio(TimeRange range); diff --git a/app/shaders/multicam.frag b/app/shaders/multicam.frag new file mode 100644 index 000000000..f3230598f --- /dev/null +++ b/app/shaders/multicam.frag @@ -0,0 +1,7 @@ +uniform int rows; +uniform int cols; + +void main(void) +{ + +} diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 5f4ab85e6..ef967b7bd 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory(handmovableview) add_subdirectory(keyframeview) add_subdirectory(manageddisplay) add_subdirectory(menu) +add_subdirectory(multicam) add_subdirectory(nodecombobox) add_subdirectory(nodeparamview) add_subdirectory(nodetableview) diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 62afcb876..a5446162a 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -275,6 +275,14 @@ void ManagedDisplayWidget::SetInnerMouseTracking(bool e) } } +VideoParams ManagedDisplayWidget::GetViewportParams() const +{ + int device_width = width() * devicePixelRatioF(); + int device_height = height() * devicePixelRatioF(); + VideoParams::Format device_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()); + return VideoParams(device_width, device_height, device_format, VideoParams::kInternalChannelCount); +} + void ManagedDisplayWidget::update() { if (RenderManager::instance()->backend() == RenderManager::kOpenGL) { diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index 644ab0711..c26008e77 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -58,7 +58,8 @@ protected: virtual void initializeGL() override { connect(context(), &QOpenGLContext::aboutToBeDestroyed, - this, &ManagedDisplayWidgetOpenGL::OnDestroy); + this, &ManagedDisplayWidgetOpenGL::DestroyListener, + Qt::DirectConnection); emit OnInit(); } @@ -211,6 +212,8 @@ protected: return wrapper_ ? wrapper_->rect() : QRect(); } + VideoParams GetViewportParams() const; + protected slots: /** * @brief Called whenever the internal rendering context has been created diff --git a/app/widget/multicam/CMakeLists.txt b/app/widget/multicam/CMakeLists.txt new file mode 100644 index 000000000..ab0ea24a3 --- /dev/null +++ b/app/widget/multicam/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + widget/multicam/multicamwidget.cpp + widget/multicam/multicamwidget.h + PARENT_SCOPE +) diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp new file mode 100644 index 000000000..6fb7fc19b --- /dev/null +++ b/app/widget/multicam/multicamwidget.cpp @@ -0,0 +1,104 @@ +#include "multicamwidget.h" + +namespace olive { + +#define super ManagedDisplayWidget + +MulticamWidget::MulticamWidget(QWidget *parent) : + super{parent}, + node_(nullptr), + cacher_(nullptr), + tex_(nullptr) +{ + +} + +void MulticamWidget::SetNode(MultiCamNode *n) +{ + if (node_ == n) { + return; + } + + if (node_) { + // Disconnect + } + + node_ = n; + + if (node_) { + // Connect + } + + SetTime(time_); +} + +void MulticamWidget::SetTime(const rational &r) +{ + time_ = r; + + if (node_) { + if (cacher_) { + int sources = node_->InputArraySize(node_->kSourcesInput); + watchers_.resize(sources); + textures_.resize(sources); + + watchers_.fill(nullptr); + textures_.fill(nullptr); + + for (int i=0; iGetConnectedOutput(node_->kSourcesInput, 0)) { + auto w = new RenderTicketWatcher(this); + connect(w, &RenderTicketWatcher::Finished, this, &MulticamWidget::RenderedFrame); + w->SetTicket(cacher_->GetSingleFrame(n, time_, false)); + watchers_[i] = w; + } + } + } + } +} + +void MulticamWidget::OnInit() +{ + super::OnInit(); + + pipeline_ = renderer()->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/multicam.frag")))); +} + +void MulticamWidget::OnPaint() +{ + // Clear display surface + renderer()->ClearDestination(); + + if (tex_) { + ColorTransformJob job; + job.SetColorProcessor(color_service()); + job.SetInputTexture(tex_); + job.SetInputAlphaAssociation(kAlphaNone); + renderer()->BlitColorManaged(job, GetViewportParams()); + } +} + +void MulticamWidget::OnDestroy() +{ + pipeline_.clear(); + tex_ = nullptr; + + super::OnDestroy(); +} + +void MulticamWidget::RenderedFrame() +{ + auto watcher = static_cast(sender()); + if (watcher->HasResult()) { + int index = watchers_.indexOf(watcher); + + if (index != -1) { + if ((textures_[index] = watcher->Get().value())) { + update(); + } + } + } + delete watcher; +} + +} diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h new file mode 100644 index 000000000..aa5bf05f0 --- /dev/null +++ b/app/widget/multicam/multicamwidget.h @@ -0,0 +1,59 @@ +#ifndef MULTICAMWIDGET_H +#define MULTICAMWIDGET_H + +#include "node/input/multicam/multicamnode.h" +#include "render/previewautocacher.h" +#include "widget/manageddisplay/manageddisplay.h" + +namespace olive { + +class MulticamWidget : public ManagedDisplayWidget +{ + Q_OBJECT +public: + explicit MulticamWidget(QWidget *parent = nullptr); + + MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(MulticamWidget) + + void SetNode(MultiCamNode *n); + + void ConnectCacher(PreviewAutoCacher *c) + { + cacher_ = c; + } + +public slots: + void SetTime(const rational &r); + +protected slots: + virtual void OnInit() override; + + virtual void OnPaint() override; + + virtual void OnDestroy() override; + +signals: + + +private: + MultiCamNode *node_; + + QVariant pipeline_; + + PreviewAutoCacher *cacher_; + + rational time_; + + TexturePtr tex_; + + QVector watchers_; + QVector textures_; + +private slots: + void RenderedFrame(); + +}; + +} + +#endif // MULTICAMWIDGET_H diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp index a0ba2eaab..da4136a5f 100644 --- a/app/widget/scope/histogram/histogram.cpp +++ b/app/widget/scope/histogram/histogram.cpp @@ -47,10 +47,10 @@ void HistogramScope::OnInit() void HistogramScope::OnDestroy() { - super::OnDestroy(); - pipeline_secondary_.clear(); texture_row_sums_ = nullptr; + + super::OnDestroy(); } ShaderCode HistogramScope::GenerateShaderCode() diff --git a/app/widget/scope/scopebase/scopebase.cpp b/app/widget/scope/scopebase/scopebase.cpp index bedcd986d..d758f0b42 100644 --- a/app/widget/scope/scopebase/scopebase.cpp +++ b/app/widget/scope/scopebase/scopebase.cpp @@ -52,9 +52,7 @@ void ScopeBase::DrawScope(TexturePtr managed_tex, QVariant pipeline) job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); - renderer()->Blit(pipeline, job, VideoParams(width(), height(), - static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()), - VideoParams::kInternalChannelCount)); + renderer()->Blit(pipeline, job, GetViewportParams()); } void ScopeBase::OnInit() @@ -89,11 +87,11 @@ void ScopeBase::OnPaint() void ScopeBase::OnDestroy() { - super::OnDestroy(); - managed_tex_ = nullptr; texture_ = nullptr; pipeline_.clear(); + + super::OnDestroy(); } } diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index bb6902a64..2f8f46407 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -72,9 +72,7 @@ void WaveformScope::DrawScope(TexturePtr managed_tex, QVariant pipeline) job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(managed_tex))); - renderer()->Blit(pipeline, job, VideoParams(width(), height(), - static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()), - VideoParams::kInternalChannelCount)); + renderer()->Blit(pipeline, job, GetViewportParams()); float waveform_dim_x = ceil((width() - 1.0) * waveform_scale); float waveform_dim_y = ceil((height() - 1.0) * waveform_scale); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 4c3a18551..734132dda 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -100,6 +100,8 @@ public: enable_audio_scrubbing_ = e; } + PreviewAutoCacher *GetCacher() const { return auto_cacher_; } + public slots: void Play(bool in_to_out_only); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 23d41efb1..27ea8bad4 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -361,10 +361,7 @@ void ViewerDisplayWidget::OnPaint() // We only draw if we have a pipeline if (push_mode_ != kPushNull) { // Draw texture through color transform - int device_width = width() * devicePixelRatioF(); - int device_height = height() * devicePixelRatioF(); - VideoParams::Format device_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()); - VideoParams device_params(device_width, device_height, device_format, VideoParams::kInternalChannelCount); + VideoParams device_params = GetViewportParams(); if (push_mode_ == kPushBlank) { if (blank_shader_.isNull()) { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 5b76ed1b0..9a336951e 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -87,6 +87,7 @@ MainWindow::MainWindow(QWidget *parent) : param_panel_ = new ParamPanel(this); curve_panel_ = new CurvePanel(this); sequence_viewer_panel_ = new SequenceViewerPanel(this); + multicam_panel_ = new MulticamPanel(sequence_viewer_panel_, this); pixel_sampler_panel_ = new PixelSamplerPanel(this); AppendProjectPanel(); tool_panel_ = new ToolPanel(this); @@ -107,10 +108,13 @@ MainWindow::MainWindow(QWidget *parent) : // Connect time signals together connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime); + connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime); connect(param_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); connect(param_panel_, &ParamPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime); + connect(param_panel_, &ParamPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime); connect(curve_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); connect(curve_panel_, &ParamPanel::TimeChanged, param_panel_, &NodeTablePanel::SetTime); + connect(curve_panel_, &ParamPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime); connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); @@ -489,6 +493,20 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) if (PanelManager::instance()->CurrentlyFocused(false) == panel) { UpdateNodePanelContextFromTimelinePanel(panel); + + bool found = false; + for (Block *b : blocks) { + if (ClipBlock *c = dynamic_cast(b)) { + if (MultiCamNode *m = dynamic_cast(c->GetConnectedOutput(c->kBufferIn))) { + multicam_panel_->SetNode(m); + found = true; + break; + } + } + } + if (!found) { + multicam_panel_->SetNode(nullptr); + } } } @@ -599,6 +617,7 @@ TimelinePanel* MainWindow::AppendTimelinePanel() connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTime); connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTime); connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); + connect(panel, &TimelinePanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime); connect(panel, &TimelinePanel::RequestCaptureStart, sequence_viewer_panel_, &SequenceViewerPanel::StartCapture); connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged); connect(panel, &TimelinePanel::RevealViewerInProject, this, &MainWindow::RevealViewerInProject); @@ -837,6 +856,10 @@ void MainWindow::SetDefaultLayout() scope_panel_->setFloating(true); addDockWidget(Qt::TopDockWidgetArea, scope_panel_); + multicam_panel_->hide(); + multicam_panel_->setFloating(true); + addDockWidget(Qt::TopDockWidgetArea, multicam_panel_); + sequence_viewer_panel_->show(); addDockWidget(Qt::TopDockWidgetArea, sequence_viewer_panel_); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 1f6504c6f..892d2ca54 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -25,6 +25,7 @@ #include "mainwindowlayoutinfo.h" #include "node/project/project.h" +#include "panel/multicam/multicampanel.h" #include "panel/panelmanager.h" #include "panel/audiomonitor/audiomonitor.h" #include "panel/curve/curve.h" @@ -163,6 +164,7 @@ private: PixelSamplerPanel* pixel_sampler_panel_; ScopePanel* scope_panel_; QMap viewer_panels_; + MulticamPanel *multicam_panel_; #ifdef Q_OS_WINDOWS unsigned int taskbar_btn_id_; From 15d786538ace1ed39ce88e3f586659869acf7635 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 25 Sep 2022 18:06:02 -0700 Subject: [PATCH 05/36] multicampanel: move to viewerpanelbase --- app/panel/multicam/multicampanel.cpp | 9 +-- app/panel/multicam/multicampanel.h | 16 +---- app/widget/multicam/multicamwidget.cpp | 95 +------------------------- app/widget/multicam/multicamwidget.h | 42 +----------- app/window/mainwindow/mainwindow.cpp | 25 +++++-- 5 files changed, 28 insertions(+), 159 deletions(-) diff --git a/app/panel/multicam/multicampanel.cpp b/app/panel/multicam/multicampanel.cpp index 97245437a..322b51458 100644 --- a/app/panel/multicam/multicampanel.cpp +++ b/app/panel/multicam/multicampanel.cpp @@ -2,16 +2,13 @@ namespace olive { -#define super PanelWidget +#define super ViewerPanelBase -MulticamPanel::MulticamPanel(ViewerPanelBase *sibling, QWidget *parent) : +MulticamPanel::MulticamPanel(QWidget *parent) : super(QStringLiteral("MultiCamPanel"), parent) { widget_ = new MulticamWidget(); - SetWidgetWithPadding(widget_); - - connect(sibling, &ViewerPanelBase::ColorManagerChanged, widget_, &MulticamWidget::ConnectColorManager); - widget_->ConnectCacher(static_cast(sibling->GetTimeBasedWidget())->GetCacher()); + SetViewerWidget(widget_); Retranslate(); } diff --git a/app/panel/multicam/multicampanel.h b/app/panel/multicam/multicampanel.h index 61a8efed7..1523d5ddc 100644 --- a/app/panel/multicam/multicampanel.h +++ b/app/panel/multicam/multicampanel.h @@ -3,26 +3,14 @@ #include "panel/viewer/viewerbase.h" #include "widget/multicam/multicamwidget.h" -#include "widget/panel/panel.h" namespace olive { -class MulticamPanel : public PanelWidget +class MulticamPanel : public ViewerPanelBase { Q_OBJECT public: - MulticamPanel(ViewerPanelBase *sibling, QWidget* parent = nullptr); - - void SetNode(MultiCamNode *n) - { - widget_->SetNode(n); - } - -public slots: - void SetTime(const rational &t) - { - widget_->SetTime(t); - } + MulticamPanel(QWidget* parent = nullptr); protected: virtual void Retranslate() override; diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 6fb7fc19b..8c2af4dd0 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -2,103 +2,12 @@ namespace olive { -#define super ManagedDisplayWidget +#define super ViewerWidget MulticamWidget::MulticamWidget(QWidget *parent) : - super{parent}, - node_(nullptr), - cacher_(nullptr), - tex_(nullptr) + super{parent} { } -void MulticamWidget::SetNode(MultiCamNode *n) -{ - if (node_ == n) { - return; - } - - if (node_) { - // Disconnect - } - - node_ = n; - - if (node_) { - // Connect - } - - SetTime(time_); -} - -void MulticamWidget::SetTime(const rational &r) -{ - time_ = r; - - if (node_) { - if (cacher_) { - int sources = node_->InputArraySize(node_->kSourcesInput); - watchers_.resize(sources); - textures_.resize(sources); - - watchers_.fill(nullptr); - textures_.fill(nullptr); - - for (int i=0; iGetConnectedOutput(node_->kSourcesInput, 0)) { - auto w = new RenderTicketWatcher(this); - connect(w, &RenderTicketWatcher::Finished, this, &MulticamWidget::RenderedFrame); - w->SetTicket(cacher_->GetSingleFrame(n, time_, false)); - watchers_[i] = w; - } - } - } - } -} - -void MulticamWidget::OnInit() -{ - super::OnInit(); - - pipeline_ = renderer()->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/multicam.frag")))); -} - -void MulticamWidget::OnPaint() -{ - // Clear display surface - renderer()->ClearDestination(); - - if (tex_) { - ColorTransformJob job; - job.SetColorProcessor(color_service()); - job.SetInputTexture(tex_); - job.SetInputAlphaAssociation(kAlphaNone); - renderer()->BlitColorManaged(job, GetViewportParams()); - } -} - -void MulticamWidget::OnDestroy() -{ - pipeline_.clear(); - tex_ = nullptr; - - super::OnDestroy(); -} - -void MulticamWidget::RenderedFrame() -{ - auto watcher = static_cast(sender()); - if (watcher->HasResult()) { - int index = watchers_.indexOf(watcher); - - if (index != -1) { - if ((textures_[index] = watcher->Get().value())) { - update(); - } - } - } - delete watcher; -} - } diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h index aa5bf05f0..19cb08510 100644 --- a/app/widget/multicam/multicamwidget.h +++ b/app/widget/multicam/multicamwidget.h @@ -3,54 +3,18 @@ #include "node/input/multicam/multicamnode.h" #include "render/previewautocacher.h" -#include "widget/manageddisplay/manageddisplay.h" +#include "widget/viewer/viewer.h" namespace olive { -class MulticamWidget : public ManagedDisplayWidget +class MulticamWidget : public ViewerWidget { Q_OBJECT public: explicit MulticamWidget(QWidget *parent = nullptr); - MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(MulticamWidget) - - void SetNode(MultiCamNode *n); - - void ConnectCacher(PreviewAutoCacher *c) - { - cacher_ = c; - } - -public slots: - void SetTime(const rational &r); - -protected slots: - virtual void OnInit() override; - - virtual void OnPaint() override; - - virtual void OnDestroy() override; - -signals: - - private: - MultiCamNode *node_; - - QVariant pipeline_; - - PreviewAutoCacher *cacher_; - - rational time_; - - TexturePtr tex_; - - QVector watchers_; - QVector textures_; - -private slots: - void RenderedFrame(); + //MultiCamNode *node_; }; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 9a336951e..c8595a64a 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -87,7 +87,7 @@ MainWindow::MainWindow(QWidget *parent) : param_panel_ = new ParamPanel(this); curve_panel_ = new CurvePanel(this); sequence_viewer_panel_ = new SequenceViewerPanel(this); - multicam_panel_ = new MulticamPanel(sequence_viewer_panel_, this); + multicam_panel_ = new MulticamPanel(this); pixel_sampler_panel_ = new PixelSamplerPanel(this); AppendProjectPanel(); tool_panel_ = new ToolPanel(this); @@ -106,6 +106,9 @@ MainWindow::MainWindow(QWidget *parent) : connect(param_panel_, &ParamPanel::SelectedNodesChanged, node_panel_, &NodePanel::Select); // Connect time signals together + connect(multicam_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime); + connect(multicam_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime); + connect(multicam_panel_, &SequenceViewerPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime); @@ -120,6 +123,7 @@ MainWindow::MainWindow(QWidget *parent) : sequence_viewer_panel_->ConnectTimeBasedPanel(param_panel_); sequence_viewer_panel_->ConnectTimeBasedPanel(curve_panel_); + sequence_viewer_panel_->ConnectTimeBasedPanel(multicam_panel_); scope_panel_->SetViewerPanel(sequence_viewer_panel_); @@ -494,18 +498,24 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) if (PanelManager::instance()->CurrentlyFocused(false) == panel) { UpdateNodePanelContextFromTimelinePanel(panel); - bool found = false; + MultiCamNode *multicam = nullptr; + for (Block *b : blocks) { if (ClipBlock *c = dynamic_cast(b)) { - if (MultiCamNode *m = dynamic_cast(c->GetConnectedOutput(c->kBufferIn))) { - multicam_panel_->SetNode(m); - found = true; + if ((multicam = dynamic_cast(c->GetConnectedOutput(c->kBufferIn)))) { break; } } } - if (!found) { - multicam_panel_->SetNode(nullptr); + + if (multicam) { + qDebug() << "Found multicam node!"; + //multicam_panel_->SetNode(multicam); + multicam_panel_->ConnectViewerNode(panel->GetConnectedViewer()); + } else { + qDebug() << "Found NO multicam node"; + multicam_panel_->ConnectViewerNode(nullptr); + //multicam_panel_->SetNode(nullptr); } } } @@ -624,6 +634,7 @@ TimelinePanel* MainWindow::AppendTimelinePanel() connect(panel, &TimelinePanel::RevealViewerInFootageViewer, this, &MainWindow::RevealViewerInFootageViewer); connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); + connect(multicam_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTime); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTime); sequence_viewer_panel_->ConnectTimeBasedPanel(panel); From ed58d54fa7d0a60e5f9ee4b0d3bf12a93d8ab8c9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 25 Sep 2022 18:06:17 -0700 Subject: [PATCH 06/36] track: return no active elements if block is disabled --- app/node/output/track/track.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index b979357d6..527b019bc 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -121,7 +121,11 @@ Node::ActiveElements Track::GetActiveElementsAtTime(const QString &input, const } } - return a; + if (a.elements().empty()) { + return ActiveElements::kNoElements; + } else { + return a; + } } } else { return super::GetActiveElementsAtTime(input, r); From e4c2f37adcae44dc2a7bbdcbbe2ea4a017dc12ac Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 25 Sep 2022 18:06:27 -0700 Subject: [PATCH 07/36] multicamnode: offset 1 and min 0 --- app/node/input/multicam/multicamnode.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index 827d035d8..f125131b3 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -11,6 +11,10 @@ MultiCamNode::MultiCamNode() { AddInput(kCurrentInput, NodeValue::kInt, InputFlags(kInputFlagStatic)); + // Make current index start at 1 instead of 0 + SetInputProperty(kCurrentInput, QStringLiteral("offset"), 1); + SetInputProperty(kCurrentInput, QStringLiteral("min"), 0); + AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); } From a6b8b7ccbc2afe558ab5374024804b2808b21e9f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 25 Sep 2022 20:26:10 -0700 Subject: [PATCH 08/36] multicam: finished usable implementation --- app/node/input/multicam/multicamnode.cpp | 114 ++++++++++++++++++++++- app/node/input/multicam/multicamnode.h | 23 +++++ app/panel/multicam/multicampanel.h | 5 + app/render/previewautocacher.cpp | 16 +++- app/render/previewautocacher.h | 6 ++ app/shaders/multicam.frag | 7 -- app/widget/multicam/multicamwidget.cpp | 42 ++++++++- app/widget/multicam/multicamwidget.h | 13 ++- app/widget/viewer/viewer.cpp | 4 +- app/widget/viewer/viewer.h | 7 ++ app/widget/viewer/viewerdisplay.cpp | 49 +++++++--- app/widget/viewer/viewerdisplay.h | 8 +- app/window/mainwindow/mainwindow.cpp | 6 +- 13 files changed, 263 insertions(+), 37 deletions(-) delete mode 100644 app/shaders/multicam.frag diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index f125131b3..16edb4ac2 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -16,6 +16,9 @@ MultiCamNode::MultiCamNode() SetInputProperty(kCurrentInput, QStringLiteral("min"), 0); AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); + SetInputProperty(kSourcesInput, QStringLiteral("arraystart"), 1); + + monitor_ = false; } QString MultiCamNode::Name() const @@ -40,7 +43,7 @@ QString MultiCamNode::Description() const Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, const TimeRange &r) const { - if (input == kSourcesInput) { + if (input == kSourcesInput && !monitor_) { Node::ActiveElements a; a.add(GetStandardValue(kCurrentInput).toInt()); return a; @@ -49,11 +52,98 @@ Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, } } +QString dblToGlsl(double d) +{ + return QString::number(d, 'f'); +} + +ShaderCode MultiCamNode::GetShaderCode(const ShaderRequest &id) const +{ + QStringList pieces = id.id.split(','); + int rows = pieces.at(0).toInt(); + int cols = pieces.at(1).toInt(); + int multiplier = std::max(cols, rows); + + QStringList shader; + + shader.append(QStringLiteral("in vec2 ove_texcoord;")); + shader.append(QStringLiteral("out vec4 frag_color;")); + + for (int x=0;x 0) { + shader.append(QStringLiteral(" else")); + } + if (x == cols-1) { + shader.append(QStringLiteral(" {")); + } else { + shader.append(QStringLiteral(" if (ove_texcoord.x < %1) {").arg(dblToGlsl(double(x+1)/double(multiplier)))); + } + + for (int y=0;y 0) { + shader.append(QStringLiteral(" else")); + } + if (y == rows-1) { + shader.append(QStringLiteral(" {")); + } else { + shader.append(QStringLiteral(" if (ove_texcoord.y < %1) {").arg(dblToGlsl(double(y+1)/double(multiplier)))); + } + QString input = QStringLiteral("tex_%1_%2").arg(QString::number(y), QString::number(x)); + shader.append(QStringLiteral(" vec2 coord = vec2((ove_texcoord.x+%1)*%2, (ove_texcoord.y+%3)*%4);").arg( + dblToGlsl( - double(x)/double(multiplier)), + dblToGlsl(multiplier), + dblToGlsl( - double(y)/double(multiplier)), + dblToGlsl(multiplier) + )); + shader.append(QStringLiteral(" if (%1_enabled && coord.x >= 0.0 && coord.x < 1.0 && coord.y >= 0.0 && coord.y < 1.0) {").arg(input)); + shader.append(QStringLiteral(" frag_color = texture(%1, coord);").arg(input)); + shader.append(QStringLiteral(" } else {")); + shader.append(QStringLiteral(" discard;")); + shader.append(QStringLiteral(" }")); + shader.append(QStringLiteral(" }")); + } + + shader.append(QStringLiteral(" }")); + } + + shader.append(QStringLiteral("}")); + + return ShaderCode(shader.join('\n')); +} + void MultiCamNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - NodeValueArray arr = value[kSourcesInput].toArray(); - if (!arr.empty()) { - table->Push(arr.begin()->second); + if (!monitor_) { + NodeValueArray arr = value[kSourcesInput].toArray(); + if (!arr.empty()) { + table->Push(arr.begin()->second); + } + } else { + NodeValueArray arr = value[kSourcesInput].toArray(); + + int rows, cols; + GetRowsAndColumns(arr.size(), &rows, &cols); + + ShaderJob job; + + job.SetShaderID(QStringLiteral("%1,%2").arg(QString::number(rows), QString::number(cols))); + + for (size_t i=0; iPush(NodeValue::kTexture, Texture::Job(globals.vparams(), job), this); } } @@ -65,4 +155,20 @@ void MultiCamNode::Retranslate() SetInputName(kSourcesInput, tr("Sources")); } +void MultiCamNode::GetRowsAndColumns(int sources, int *rows_in, int *cols_in) +{ + int &rows = *rows_in; + int &cols = *cols_in; + + rows = 1; + cols = 1; + while (rows*cols < sources) { + if (rows < cols) { + rows++; + } else { + cols++; + } + } +} + } diff --git a/app/node/input/multicam/multicamnode.h b/app/node/input/multicam/multicamnode.h index d8895a7f5..5adf5eb12 100644 --- a/app/node/input/multicam/multicamnode.h +++ b/app/node/input/multicam/multicamnode.h @@ -20,6 +20,8 @@ public: virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &id) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; virtual void Retranslate() override; @@ -27,6 +29,27 @@ public: static const QString kCurrentInput; static const QString kSourcesInput; + void SetMonitorMode(bool e) { monitor_ = e; } + + int GetSourceCount() const + { + return InputArraySize(kSourcesInput); + } + + static void GetRowsAndColumns(int sources, int *rows, int *cols); + void GetRowsAndColumns(int *rows, int *cols) const + { + return GetRowsAndColumns(GetSourceCount(), rows, cols); + } + + static int RowsColsToIndex(int row, int col, int total_rows, int total_cols) + { + return col + row * total_cols; + } + +private: + bool monitor_; + }; } diff --git a/app/panel/multicam/multicampanel.h b/app/panel/multicam/multicampanel.h index 1523d5ddc..b8634bbfc 100644 --- a/app/panel/multicam/multicampanel.h +++ b/app/panel/multicam/multicampanel.h @@ -12,6 +12,11 @@ class MulticamPanel : public ViewerPanelBase public: MulticamPanel(QWidget* parent = nullptr); + void SetMulticamNode(MultiCamNode *n) + { + widget_->SetMulticamNode(n); + } + protected: virtual void Retranslate() override; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 9a0de0898..584405fd1 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -24,6 +24,7 @@ #include #include "codec/conformmanager.h" +#include "node/input/multicam/multicamnode.h" #include "node/inputdragger.h" #include "node/project/project.h" #include "render/diskmanager.h" @@ -41,7 +42,9 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) : use_custom_range_(false), pause_renders_(false), single_frame_render_(nullptr), - display_color_processor_(nullptr) + display_color_processor_(nullptr), + multicam_mode_(false), + ignore_cache_requests_(false) { // Set defaults SetPlayhead(0); @@ -285,6 +288,13 @@ void PreviewAutoCacher::AddNode(Node *node) // Copy node Node* copy = node->copy(); + // Fairly hacky way of getting multicam nodes to produce a monitor rather than a single source + if (multicam_mode_) { + if (MultiCamNode *m = dynamic_cast(copy)) { + m->SetMonitorMode(true); + } + } + // Add to project copy->setParent(&copied_project_); @@ -368,7 +378,9 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) Node::CopyInputs(node, copy, false); // Connect to node's cache - ConnectToNodeCache(node); + if (ignore_cache_requests_) { + ConnectToNodeCache(node); + } } void PreviewAutoCacher::ConnectToNodeCache(Node *node) diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 6f0fc6ac7..9f68bb9e5 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -90,6 +90,9 @@ public: void SetRendersPaused(bool e); + void SetMulticamMode(bool e) { multicam_mode_ = e; } + void SetIgnoreCacheRequests(bool e) { ignore_cache_requests_ = e; } + public slots: void SetDisplayColorProcessor(ColorProcessorPtr processor) { @@ -218,6 +221,9 @@ private: ColorProcessorPtr display_color_processor_; + bool multicam_mode_; + bool ignore_cache_requests_; + private slots: /** * @brief Handler for when the NodeGraph reports a video change over a certain time range diff --git a/app/shaders/multicam.frag b/app/shaders/multicam.frag deleted file mode 100644 index f3230598f..000000000 --- a/app/shaders/multicam.frag +++ /dev/null @@ -1,7 +0,0 @@ -uniform int rows; -uniform int cols; - -void main(void) -{ - -} diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 8c2af4dd0..ab423ed1d 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -1,13 +1,53 @@ #include "multicamwidget.h" +#include "widget/nodeparamview/nodeparamviewundo.h" namespace olive { #define super ViewerWidget MulticamWidget::MulticamWidget(QWidget *parent) : - super{parent} + super{parent}, + node_(nullptr) { + auto_cacher()->SetMulticamMode(true); + connect(display_widget(), &ViewerDisplayWidget::DragStarted, this, &MulticamWidget::DisplayClicked); +} + +RenderTicketPtr MulticamWidget::GetSingleFrame(const rational &t, bool dry) +{ + if (node_) { + return auto_cacher()->GetSingleFrame(node_, t, dry); + } else { + return super::GetSingleFrame(t, dry); + } +} + +void MulticamWidget::DisplayClicked(const QPoint &p) +{ + if (!node_) { + return; + } + + QPointF click = display_widget()->ScreenToScenePoint(p); + int width = display_widget()->GetVideoParams().width(); + int height = display_widget()->GetVideoParams().height(); + + if (click.x() < 0 || click.y() < 0 || click.x() >= width || click.y() >= height) { + return; + } + + int rows, cols; + node_->GetRowsAndColumns(&rows, &cols); + + int multi = std::max(cols, rows); + + int c = click.x() / (width/multi); + int r = click.y() / (height/multi); + + MultiUndoCommand *command = new MultiUndoCommand(); + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(node_, node_->kCurrentInput)), node_->RowsColsToIndex(r, c, rows, cols))); + Core::instance()->undo_stack()->push(command); } } diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h index 19cb08510..f53e77e9c 100644 --- a/app/widget/multicam/multicamwidget.h +++ b/app/widget/multicam/multicamwidget.h @@ -13,8 +13,19 @@ class MulticamWidget : public ViewerWidget public: explicit MulticamWidget(QWidget *parent = nullptr); + void SetMulticamNode(MultiCamNode *n) + { + node_ = n; + } + +protected: + virtual RenderTicketPtr GetSingleFrame(const rational &t, bool dry = false) override; + private: - //MultiCamNode *node_; + MultiCamNode *node_; + +private slots: + void DisplayClicked(const QPoint &p); }; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 134874425..e70fb7387 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -580,7 +580,7 @@ void ViewerWidget::RequestNextDryRun() } else { RenderTicketWatcher *watcher = new RenderTicketWatcher(this); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::DryRunFinished); - watcher->SetTicket(auto_cacher_->GetSingleFrame(next_time, true)); + watcher->SetTicket(GetSingleFrame(next_time, true)); dry_run_next_frame_ += playback_speed_; dry_run_watchers_.append(watcher); } @@ -1033,7 +1033,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t) if (!QFileInfo::exists(cache_fn)) { // Frame hasn't been cached, start render job - return auto_cacher_->GetSingleFrame(t); + return GetSingleFrame(t); } else { // Frame has been cached, grab the frame RenderTicketPtr ticket = std::make_shared(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 734132dda..fe3e44ed4 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -184,6 +184,13 @@ protected: ignore_scrub_++; } + virtual RenderTicketPtr GetSingleFrame(const rational &t, bool dry = false) + { + return auto_cacher_->GetSingleFrame(t, dry); + } + + PreviewAutoCacher *auto_cacher() const { return auto_cacher_; } + private: int64_t GetTimestamp() const { diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 27ea8bad4..c3a811fd3 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -431,17 +431,13 @@ void ViewerDisplayWidget::OnPaint() // Draw gizmos if we have any if (gizmos_) { - NodeTraverser gt; - gt.SetCacheVideoParams(gizmo_params_); - - TimeRange range = GenerateGizmoTime(); - gizmo_db_ = gt.GenerateRow(gizmos_, range); - QPainter p(paint_device()); - gizmo_last_draw_transform_ = GenerateGizmoTransform(gt, range); + + GenerateGizmoTransforms(); + p.setWorldTransform(gizmo_last_draw_transform_); - gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, range)); + gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, gizmo_draw_time_)); foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) { if (gizmo->IsVisible()) { gizmo->Draw(&p); @@ -812,8 +808,7 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event) add_band_ = true; } else if (gizmos_ - && (gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted(), - current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) { + && (current_gizmo_ = TryGizmoPress(gizmo_db_, gizmo_last_draw_transform_inverted_.map(event->pos())))) { // Handle gizmo click gizmo_start_drag_ = event->pos(); @@ -823,7 +818,7 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event) } else { // Handle standard drag - emit DragStarted(); + emit DragStarted(event->pos()); } @@ -871,7 +866,7 @@ bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event) // Signal movement if (DraggableGizmo *draggable = dynamic_cast(current_gizmo_)) { if (!gizmo_drag_started_) { - QPointF start = gizmo_start_drag_ * gizmo_last_draw_transform_inverted_; + QPointF start = ScreenToScenePoint(gizmo_start_drag_); rational gizmo_time = GetGizmoTime(); NodeTraverser t; @@ -882,17 +877,17 @@ bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event) gizmo_drag_started_ = true; } - QPointF v = event->pos() * gizmo_last_draw_transform_inverted_; + QPointF v = ScreenToScenePoint(event->pos()); switch (draggable->GetDragValueBehavior()) { case DraggableGizmo::kAbsolute: // Above value is correct break; case DraggableGizmo::kDeltaFromPrevious: - v -= gizmo_last_drag_ * gizmo_last_draw_transform_inverted_; + v -= ScreenToScenePoint(gizmo_last_drag_); gizmo_last_drag_ = event->pos(); break; case DraggableGizmo::kDeltaFromStart: - v -= gizmo_start_drag_ * gizmo_last_draw_transform_inverted_; + v -= ScreenToScenePoint(gizmo_start_drag_); break; } @@ -1182,6 +1177,21 @@ void ViewerDisplayWidget::CloseTextEditor() text_edit_ = nullptr; } +void ViewerDisplayWidget::GenerateGizmoTransforms() +{ + NodeTraverser gt; + gt.SetCacheVideoParams(gizmo_params_); + + gizmo_draw_time_ = GenerateGizmoTime(); + + if (gizmos_) { + gizmo_db_ = gt.GenerateRow(gizmos_, gizmo_draw_time_); + } + + gizmo_last_draw_transform_ = GenerateGizmoTransform(gt, gizmo_draw_time_); + gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted(); +} + void ViewerDisplayWidget::SetShowFPS(bool e) { show_fps_ = e; @@ -1221,6 +1231,15 @@ void ViewerDisplayWidget::Pause() queue_starved_ = false; } +QPointF ViewerDisplayWidget::ScreenToScenePoint(const QPoint &p) +{ + if (gizmo_last_draw_transform_.isIdentity()) { + GenerateGizmoTransforms(); + } + + return p * gizmo_last_draw_transform_inverted_; +} + void ViewerDisplayWidget::UpdateFromQueue() { int64_t t = timer_.GetTimestampNow(); diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index ab39a2b10..04e9f6e15 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -74,6 +74,7 @@ public: void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin); void SetGizmos(Node* node); + const VideoParams &GetVideoParams() const { return gizmo_params_; } void SetVideoParams(const VideoParams ¶ms); void SetTime(const rational& time); void SetSubtitleTracks(Sequence *list); @@ -131,6 +132,8 @@ public: return &timer_; } + QPointF ScreenToScenePoint(const QPoint &p); + virtual bool eventFilter(QObject *o, QEvent *e) override; public slots: @@ -182,7 +185,7 @@ signals: /** * @brief Signal emitted when the user starts dragging from the viewer */ - void DragStarted(); + void DragStarted(const QPoint &p); /** * @brief Signal emitted when a hand drag starts @@ -290,6 +293,8 @@ private: void CloseTextEditor(); + void GenerateGizmoTransforms(); + /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). */ @@ -340,6 +345,7 @@ private: VideoParams gizmo_params_; QPoint gizmo_start_drag_; QPoint gizmo_last_drag_; + TimeRange gizmo_draw_time_; NodeGizmo *current_gizmo_; bool gizmo_drag_started_; QTransform gizmo_last_draw_transform_; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index c8595a64a..a4aefc1b6 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -509,13 +509,11 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) } if (multicam) { - qDebug() << "Found multicam node!"; - //multicam_panel_->SetNode(multicam); + multicam_panel_->SetMulticamNode(multicam); multicam_panel_->ConnectViewerNode(panel->GetConnectedViewer()); } else { - qDebug() << "Found NO multicam node"; multicam_panel_->ConnectViewerNode(nullptr); - //multicam_panel_->SetNode(nullptr); + multicam_panel_->SetMulticamNode(nullptr); } } } From 01c3c6050dede35472c862509e80128ed20f2364 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 27 Sep 2022 13:46:42 -0700 Subject: [PATCH 09/36] restored audio functionality --- app/node/globals.h | 6 +- app/node/output/track/track.cpp | 125 ++++++++++++++-------------- app/node/output/track/track.h | 2 +- app/node/traverser.cpp | 6 +- app/node/traverser.h | 6 +- app/widget/viewer/viewer.cpp | 6 ++ app/widget/viewer/viewerdisplay.cpp | 13 ++- app/widget/viewer/viewerdisplay.h | 6 ++ 8 files changed, 96 insertions(+), 74 deletions(-) diff --git a/app/node/globals.h b/app/node/globals.h index 4db941be9..c7ec1ce59 100644 --- a/app/node/globals.h +++ b/app/node/globals.h @@ -24,6 +24,7 @@ #include #include "common/timerange.h" +#include "render/audioparams.h" #include "render/videoparams.h" namespace olive { @@ -33,19 +34,22 @@ class NodeGlobals public: NodeGlobals(){} - NodeGlobals(const VideoParams &vparam, const TimeRange &time) : + NodeGlobals(const VideoParams &vparam, const AudioParams &aparam, const TimeRange &time) : video_params_(vparam), + audio_params_(aparam), time_(time) { } QVector2D square_resolution() const { return video_params_.square_resolution(); } QVector2D nonsquare_resolution() const { return video_params_.resolution(); } + const AudioParams &aparams() const { return audio_params_; } const VideoParams &vparams() const { return video_params_; } const TimeRange &time() const { return time_; } private: VideoParams video_params_; + AudioParams audio_params_; TimeRange time_; }; diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 527b019bc..3d242077a 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -116,7 +116,7 @@ Node::ActiveElements Track::GetActiveElementsAtTime(const QString &input, const ActiveElements a; for (int i=start; i<=end; i++) { Block *b = blocks_.at(i); - if (b->is_enabled()) { + if (b->is_enabled() && (dynamic_cast(b) || dynamic_cast(b))) { a.add(GetArrayIndexFromCacheIndex(i)); } } @@ -142,7 +142,7 @@ void Track::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeVal } } else if (this->type() == Track::kAudio) { // Audio - ProcessAudioTrack(table, globals.time()); + ProcessAudioTrack(value, globals, table); } } @@ -603,100 +603,97 @@ int Track::GetBlockIndexAtTime(const rational &time) const return -1; } -void Track::ProcessAudioTrack(NodeValueTable *table, const TimeRange &range) const +void Track::ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - /* + const TimeRange &range = globals.time(); + // All these blocks will need to output to a buffer so we create one here - SampleBuffer block_range_buffer(audio_params, range.length()); + SampleBuffer block_range_buffer(globals.aparams(), range.length()); block_range_buffer.silence(); - NodeValueTable merged_table; - // Loop through active blocks retrieving their audio - foreach (Block* b, active_blocks) { - if (dynamic_cast(b) || dynamic_cast(b)) { - TimeRange range_for_block(qMax(b->in(), range.in()), - qMin(b->out(), range.out())); + NodeValueArray arr = value[kBlockInput].toArray(); - qint64 destination_offset = audio_params.time_to_samples(range_for_block.in() - range.in()); - qint64 max_dest_sz = audio_params.time_to_samples(range_for_block.length()); + for (auto it=arr.cbegin(); it!=arr.cend(); it++) { + Block *b = blocks_.at(GetCacheIndexFromArrayIndex(it->first)); - // Destination buffer - NodeValueTable table = GenerateTable(b, Track::TransformRangeForBlock(b, range_for_block)); - SampleBuffer samples_from_this_block = table.Take(NodeValue::kSamples).toSamples(); - ClipBlock *clip_cast = dynamic_cast(b); + TimeRange range_for_block(qMax(b->in(), range.in()), + qMin(b->out(), range.out())); - if (samples_from_this_block.is_allocated()) { - // 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(); + qint64 destination_offset = globals.aparams().time_to_samples(range_for_block.in() - range.in()); + qint64 max_dest_sz = globals.aparams().time_to_samples(range_for_block.length()); - if (qIsNull(speed_value)) { - // Just silence, don't think there's any other practical application of 0 speed audio - samples_from_this_block.silence(); - } else if (!qFuzzyCompare(speed_value, 1.0)) { - if (clip_cast->maintain_audio_pitch()) { - AudioProcessor processor; + // Destination buffer + SampleBuffer samples_from_this_block = it->second.toSamples(); + ClipBlock *clip_cast = dynamic_cast(b); - if (processor.Open(samples_from_this_block.audio_params(), samples_from_this_block.audio_params(), speed_value)) { - AudioProcessor::Buffer out; + if (samples_from_this_block.is_allocated()) { + // 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(); - // FIXME: This is not the best way to do this, the TempoProcessor works best - // when it's given a continuous stream of audio, which is challenging - // in our current "modular" audio system. This should still work reasonably - // well on export (assuming audio is all generated at once on export), but - // users may hear clicks and pops in the audio during preview due to this - // approach. - int r = processor.Convert(samples_from_this_block.to_raw_ptrs().data(), samples_from_this_block.sample_count(), nullptr); + if (qIsNull(speed_value)) { + // Just silence, don't think there's any other practical application of 0 speed audio + samples_from_this_block.silence(); + } else if (!qFuzzyCompare(speed_value, 1.0)) { + if (clip_cast->maintain_audio_pitch()) { + AudioProcessor processor; - if (r < 0) { - qCritical() << "Failed to change tempo of audio:" << r; - } else { - processor.Flush(); + if (processor.Open(samples_from_this_block.audio_params(), samples_from_this_block.audio_params(), speed_value)) { + AudioProcessor::Buffer out; - processor.Convert(nullptr, 0, &out); + // FIXME: This is not the best way to do this, the TempoProcessor works best + // when it's given a continuous stream of audio, which is challenging + // in our current "modular" audio system. This should still work reasonably + // well on export (assuming audio is all generated at once on export), but + // users may hear clicks and pops in the audio during preview due to this + // approach. + int r = processor.Convert(samples_from_this_block.to_raw_ptrs().data(), samples_from_this_block.sample_count(), nullptr); - if (!out.empty()) { - int nb_samples = out.front().size() * samples_from_this_block.audio_params().bytes_per_sample_per_channel(); + if (r < 0) { + qCritical() << "Failed to change tempo of audio:" << r; + } else { + processor.Flush(); - if (nb_samples) { - SampleBuffer new_samples(samples_from_this_block.audio_params(), nb_samples); + processor.Convert(nullptr, 0, &out); - for (int i=0; iPush(NodeValue::kSamples, QVariant::fromValue(block_range_buffer), this); - */ } void Track::BlockLengthChanged() diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index e2ac8d3bb..e6ae5584e 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -453,7 +453,7 @@ private: int GetBlockIndexAtTime(const rational &time) const; - void ProcessAudioTrack(NodeValueTable *table, const TimeRange &range) const; + void ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const; TimeRangeList block_length_pending_invalidations_; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index d962dc798..bb95a6463 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -179,9 +179,9 @@ void NodeTraverser::Transform(QTransform *transform, const Node *start, const No transform_ = nullptr; } -NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams ¶ms, const TimeRange &time) +NodeGlobals NodeTraverser::GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const TimeRange &time) { - return NodeGlobals(params, time); + return NodeGlobals(vparams, aparams, time); } NodeValueTable NodeTraverser::ProcessInput(const Node* node, const QString& input, const TimeRange& range) @@ -304,7 +304,7 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const TimeRange& rang table = database.Merge(); // By this point, the node should have all the inputs it needs to render correctly - NodeGlobals globals = GenerateGlobals(video_params_, range); + NodeGlobals globals = GenerateGlobals(video_params_, audio_params_, range); n->Value(row, globals, &table); // `transform_now_` is the next node in the path that needs to be traversed. It only ever goes diff --git a/app/node/traverser.h b/app/node/traverser.h index cb44bfc44..e34a708b5 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -54,10 +54,10 @@ public: void Transform(QTransform *transform, const Node *start, const Node *end, const TimeRange &range); - static NodeGlobals GenerateGlobals(const VideoParams ¶ms, const TimeRange &time); - static NodeGlobals GenerateGlobals(const VideoParams ¶ms, const rational &time) + static NodeGlobals GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const TimeRange &time); + static NodeGlobals GenerateGlobals(const VideoParams &vparams, const AudioParams &aparams, const rational &time) { - return GenerateGlobals(params, TimeRange(time, time + params.frame_rate_as_time_base())); + return GenerateGlobals(vparams, aparams, TimeRange(time, time + vparams.frame_rate_as_time_base())); } const VideoParams& GetCacheVideoParams() const diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index e70fb7387..d45955f9f 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1678,7 +1678,13 @@ void ViewerWidget::UpdateRendererVideoParameters() void ViewerWidget::UpdateRendererAudioParameters() { + AudioParams ap = GetConnectedNode()->GetAudioParams(); + UpdateAudioProcessor(); + + foreach (ViewerDisplayWidget *dw, playback_devices_) { + dw->SetAudioParams(ap); + } } void ViewerWidget::SetZoomFromMenu(QAction *action) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index c3a811fd3..bd1d8bc59 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -191,6 +191,15 @@ void ViewerDisplayWidget::SetVideoParams(const VideoParams ¶ms) } } +void ViewerDisplayWidget::SetAudioParams(const AudioParams ¶ms) +{ + gizmo_audio_params_ = params; + + if (gizmos_) { + update(); + } +} + void ViewerDisplayWidget::SetTime(const rational &time) { time_ = time; @@ -437,7 +446,7 @@ void ViewerDisplayWidget::OnPaint() p.setWorldTransform(gizmo_last_draw_transform_); - gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, gizmo_draw_time_)); + gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, gizmo_audio_params_, gizmo_draw_time_)); foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) { if (gizmo->IsVisible()) { gizmo->Draw(&p); @@ -813,7 +822,7 @@ bool ViewerDisplayWidget::OnMousePress(QMouseEvent *event) // Handle gizmo click gizmo_start_drag_ = event->pos(); gizmo_last_drag_ = gizmo_start_drag_; - current_gizmo_->SetGlobals(NodeTraverser::GenerateGlobals(gizmo_params_, GenerateGizmoTime())); + current_gizmo_->SetGlobals(NodeTraverser::GenerateGlobals(gizmo_params_, gizmo_audio_params_, GenerateGizmoTime())); } else { diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 04e9f6e15..d11ef585a 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -74,8 +74,13 @@ public: void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin); void SetGizmos(Node* node); + const VideoParams &GetVideoParams() const { return gizmo_params_; } void SetVideoParams(const VideoParams ¶ms); + + const AudioParams &GetAudioParams() const { return gizmo_audio_params_; } + void SetAudioParams(const AudioParams &p); + void SetTime(const rational& time); void SetSubtitleTracks(Sequence *list); @@ -343,6 +348,7 @@ private: Node* gizmos_; NodeValueRow gizmo_db_; VideoParams gizmo_params_; + AudioParams gizmo_audio_params_; QPoint gizmo_start_drag_; QPoint gizmo_last_drag_; TimeRange gizmo_draw_time_; From c0d8d24c47f1bd5f10fe52bbc0d753e1583f0b8a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 27 Sep 2022 17:01:40 -0700 Subject: [PATCH 10/36] viewer: fix incorrect if statement bug --- app/render/previewautocacher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 584405fd1..4bb23ee7f 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -378,7 +378,7 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) Node::CopyInputs(node, copy, false); // Connect to node's cache - if (ignore_cache_requests_) { + if (!ignore_cache_requests_) { ConnectToNodeCache(node); } } From 80a900d1057e8e81dd4dba182d5591fc626820b1 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 27 Sep 2022 18:22:47 -0700 Subject: [PATCH 11/36] multicam: show highlight around selected source --- app/node/input/multicam/multicamnode.cpp | 16 ++++-- app/node/input/multicam/multicamnode.h | 7 +++ app/widget/multicam/CMakeLists.txt | 2 + app/widget/multicam/multicamdisplay.cpp | 63 ++++++++++++++++++++++++ app/widget/multicam/multicamdisplay.h | 47 ++++++++++++++++++ app/widget/multicam/multicamwidget.cpp | 29 ++++++++++- app/widget/multicam/multicamwidget.h | 27 ++++++++-- app/widget/viewer/viewer.cpp | 4 +- app/widget/viewer/viewer.h | 6 ++- app/widget/viewer/viewerdisplay.h | 37 +++++++------- 10 files changed, 207 insertions(+), 31 deletions(-) create mode 100644 app/widget/multicam/multicamdisplay.cpp create mode 100644 app/widget/multicam/multicamdisplay.h diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index 16edb4ac2..b468af862 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -45,7 +45,7 @@ Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, { if (input == kSourcesInput && !monitor_) { Node::ActiveElements a; - a.add(GetStandardValue(kCurrentInput).toInt()); + a.add(GetCurrentSource()); return a; } else { return super::GetActiveElementsAtTime(input, r); @@ -137,9 +137,9 @@ void MultiCamNode::Value(const NodeValueRow &value, const NodeGlobals &globals, job.SetShaderID(QStringLiteral("%1,%2").arg(QString::number(rows), QString::number(cols))); - for (size_t i=0; i. + +***/ + +#include "multicamdisplay.h" + +namespace olive { + +#define super ViewerDisplayWidget + +MulticamDisplay::MulticamDisplay(QWidget *parent) : + super(parent), + node_(nullptr) +{ +} + +void MulticamDisplay::OnPaint() +{ + super::OnPaint(); + + if (node_) { + QPainter p(paint_device()); + + p.setPen(QPen(Qt::yellow, fontMetrics().height()/4)); + p.setBrush(Qt::NoBrush); + + int rows, cols; + node_->GetRowsAndColumns(&rows, &cols); + + int multi = std::max(rows, cols); + int cell_width = width() / multi; + int cell_height = height() / multi; + + int col, row; + node_->IndexToRowCols(node_->GetCurrentSource(), rows, cols, &row, &col); + + QRect r(cell_width * col, cell_height * row, cell_width, cell_height); + p.drawRect(GenerateWorldTransform().mapRect(r)); + } +} + +void MulticamDisplay::SetMulticamNode(MultiCamNode *n) +{ + node_ = n; +} + +} diff --git a/app/widget/multicam/multicamdisplay.h b/app/widget/multicam/multicamdisplay.h new file mode 100644 index 000000000..2898142d1 --- /dev/null +++ b/app/widget/multicam/multicamdisplay.h @@ -0,0 +1,47 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef MULTICAMDISPLAY_H +#define MULTICAMDISPLAY_H + +#include "node/input/multicam/multicamnode.h" +#include "widget/viewer/viewerdisplay.h" + +namespace olive { + +class MulticamDisplay : public ViewerDisplayWidget +{ + Q_OBJECT +public: + explicit MulticamDisplay(QWidget *parent = nullptr); + + void SetMulticamNode(MultiCamNode *n); + +protected: + virtual void OnPaint() override; + +private: + MultiCamNode *node_; + +}; + +} + +#endif // MULTICAMDISPLAY_H diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index ab423ed1d..3ff424011 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "multicamwidget.h" #include "widget/nodeparamview/nodeparamviewundo.h" @@ -6,14 +26,21 @@ namespace olive { #define super ViewerWidget MulticamWidget::MulticamWidget(QWidget *parent) : - super{parent}, + super{new MulticamDisplay(), parent}, node_(nullptr) { auto_cacher()->SetMulticamMode(true); + auto_cacher()->SetIgnoreCacheRequests(true); connect(display_widget(), &ViewerDisplayWidget::DragStarted, this, &MulticamWidget::DisplayClicked); } +void MulticamWidget::SetMulticamNode(MultiCamNode *n) +{ + node_ = n; + static_cast(display_widget())->SetMulticamNode(n); +} + RenderTicketPtr MulticamWidget::GetSingleFrame(const rational &t, bool dry) { if (node_) { diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h index f53e77e9c..b80414c97 100644 --- a/app/widget/multicam/multicamwidget.h +++ b/app/widget/multicam/multicamwidget.h @@ -1,8 +1,28 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef MULTICAMWIDGET_H #define MULTICAMWIDGET_H +#include "multicamdisplay.h" #include "node/input/multicam/multicamnode.h" -#include "render/previewautocacher.h" #include "widget/viewer/viewer.h" namespace olive { @@ -13,10 +33,7 @@ class MulticamWidget : public ViewerWidget public: explicit MulticamWidget(QWidget *parent = nullptr); - void SetMulticamNode(MultiCamNode *n) - { - node_ = n; - } + void SetMulticamNode(MultiCamNode *n); protected: virtual RenderTicketPtr GetSingleFrame(const rational &t, bool dry = false) override; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index d45955f9f..bdf28a4de 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -62,7 +62,7 @@ const rational ViewerWidget::kAudioPlaybackInterval = rational(1, 4); const rational kVideoPlaybackInterval = rational(1, 2); -ViewerWidget::ViewerWidget(QWidget *parent) : +ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) : super(false, true, parent), playback_speed_(0), color_menu_enabled_(true), @@ -85,7 +85,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : sizer_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout->addWidget(sizer_); - display_widget_ = new ViewerDisplayWidget(); + display_widget_ = display; display_widget_->SetShowWidgetBackground(true); playback_devices_.append(display_widget_); connect(display_widget_, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index fe3e44ed4..400853652 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -57,7 +57,9 @@ public: kWFViewerAndWaveform }; - ViewerWidget(QWidget* parent = nullptr); + ViewerWidget(QWidget* parent = nullptr) : + ViewerWidget(new ViewerDisplayWidget(), parent) + {} virtual ~ViewerWidget() override; @@ -159,6 +161,8 @@ signals: void ColorManagerChanged(ColorManager* color_manager); protected: + ViewerWidget(ViewerDisplayWidget *display, QWidget* parent = nullptr); + virtual void TimebaseChangedEvent(const rational &) override; virtual void TimeChangedEvent(const rational &time) override; diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index d11ef585a..077ea6023 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -226,6 +226,25 @@ signals: void CreateAddableAt(const QRectF &rect); +protected: + QTransform GenerateWorldTransform(); + + QTransform GenerateDisplayTransform(); + + QTransform GenerateGizmoTransform(NodeTraverser >, const TimeRange &range); + QTransform GenerateGizmoTransform() + { + NodeTraverser t; + t.SetCacheVideoParams(gizmo_params_); + return GenerateGizmoTransform(t, GenerateGizmoTime()); + } + + TimeRange GenerateGizmoTime() + { + rational node_time = GetGizmoTime(); + return TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base()); + } + protected slots: /** * @brief Paint function to display the texture (received in SetTexture()) on screen. @@ -249,24 +268,6 @@ private: void UpdateMatrix(); - QTransform GenerateWorldTransform(); - - QTransform GenerateDisplayTransform(); - - QTransform GenerateGizmoTransform(NodeTraverser >, const TimeRange &range); - QTransform GenerateGizmoTransform() - { - NodeTraverser t; - t.SetCacheVideoParams(gizmo_params_); - return GenerateGizmoTransform(t, GenerateGizmoTime()); - } - - TimeRange GenerateGizmoTime() - { - rational node_time = GetGizmoTime(); - return TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base()); - } - NodeGizmo *TryGizmoPress(const NodeValueRow &row, const QPointF &p); void OpenTextGizmo(TextGizmo *text, QMouseEvent *event = nullptr); From a29fa4bc4d5833dd2bc84fd21650ece41083f94d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 27 Sep 2022 18:44:00 -0700 Subject: [PATCH 12/36] fixed generator with merge issue --- app/node/generator/shape/generatorwithmerge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/generator/shape/generatorwithmerge.cpp b/app/node/generator/shape/generatorwithmerge.cpp index 8622c1881..93f8bcde3 100644 --- a/app/node/generator/shape/generatorwithmerge.cpp +++ b/app/node/generator/shape/generatorwithmerge.cpp @@ -59,7 +59,7 @@ void GeneratorWithMerge::PushMergableJob(const NodeValueRow &value, TexturePtr j merge.SetShaderID(QStringLiteral("mrg")); merge.Insert(MergeNode::kBaseIn, value[kBaseInput]); - merge.Insert(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, base->toJob(*job->job()), this)); + merge.Insert(MergeNode::kBlendIn, NodeValue(NodeValue::kTexture, job, this)); table->Push(NodeValue::kTexture, base->toJob(merge), this); } else { From 149b1b3690992a0a5a954b2fa3a010ae7b16c20b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:17:37 -0700 Subject: [PATCH 13/36] audiowaveformcache: improve passthrough system --- app/common/timerange.h | 11 +++ app/render/audiowaveformcache.cpp | 121 +++++++++++++----------------- app/render/audiowaveformcache.h | 55 +++++--------- 3 files changed, 80 insertions(+), 107 deletions(-) diff --git a/app/common/timerange.h b/app/common/timerange.h index b6235f9b8..8fc55872e 100644 --- a/app/common/timerange.h +++ b/app/common/timerange.h @@ -126,6 +126,17 @@ public: return false; } + bool OverlapsWith(const TimeRange& r, bool in_inclusive = true, bool out_inclusive = true) const + { + for (const TimeRange &range : array_) { + if (range.OverlapsWith(r, in_inclusive, out_inclusive)) { + return true; + } + } + + return false; + } + bool isEmpty() const { return array_.isEmpty(); diff --git a/app/render/audiowaveformcache.cpp b/app/render/audiowaveformcache.cpp index 5ed1ee280..4f9870759 100644 --- a/app/render/audiowaveformcache.cpp +++ b/app/render/audiowaveformcache.cpp @@ -22,113 +22,94 @@ namespace olive { +#define super PlaybackCache + AudioWaveformCache::AudioWaveformCache(QObject *parent) : - PlaybackCache{parent} + super{parent} { + waveforms_ = std::make_shared(); } void AudioWaveformCache::WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform) { // Write each valid range to the segments foreach (const TimeRange& r, valid_ranges) { -#ifdef AVW_USE_LIST - // Write visual - TimeRangeList::util_remove(&waveforms_, r); - if (waveform) { - TimeRangeWithWaveform wv = r; - rational local_start = r.in() - range.in(); - if (local_start != 0) { - wv.waveform = waveform->Mid(local_start, r.length()); - } else { - wv.waveform = *waveform; - } - waveforms_.append(wv); + waveforms_->OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length()); } -#else - if (waveform) { - waveforms_.OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length()); - } -#endif Validate(r); } } +void DrawSubRect(QPainter *painter, const QRect &rect, const double &scale, const TimeRange &wave_range, const AudioVisualWaveform &waveform, const TimeRange &subrange) +{ + // Find start time of passthrough + TimeRange intersect = wave_range.Intersected(subrange); + + // Create new rect that starts at the offset of pass_start from start_time + // Set rect width to either length of passthrough or until the end + QRect pass_rect(rect.x() + (intersect.in() - wave_range.in()).toDouble() * scale, + rect.y(), + intersect.length().toDouble() * scale, + rect.height()); + + // Draw waveform with this info + AudioVisualWaveform::DrawWaveform(painter, pass_rect, scale, waveform, intersect.in()); +} + void AudioWaveformCache::Draw(QPainter *painter, const QRect &rect, const double &scale, const rational &start_time) const { - rational end = start_time + rational::fromDouble(rect.width() / scale); - TimeRange draw_range(start_time, end); + if (!passthroughs_.empty()) { + TimeRange wave_range(start_time, start_time + rational::fromDouble(rect.width() / scale)); + TimeRangeList draw_range = {wave_range}; + for (const WaveformPassthrough &p : passthroughs_) { + if (draw_range.OverlapsWith(p, true, false)) { + DrawSubRect(painter, rect, scale, wave_range, *p.waveform, p); -#ifdef AVW_USE_LIST - foreach (const TimeRangeWithWaveform &wv, waveforms_) { - if (wv.OverlapsWith(draw_range)) { - rational substart = std::max(wv.in(), draw_range.in()); - rational subend = std::min(wv.out(), draw_range.out()); - - QRect subrect = rect; - subrect.setLeft(subrect.left() + (substart - draw_range.in()).toDouble()*scale); - subrect.setWidth((subend - substart).toDouble()*scale); - - rational local_start = substart - wv.in(); - AudioVisualWaveform::DrawWaveform(painter, subrect, scale, wv.waveform, local_start); + // Remove this range + draw_range.remove(p); + } } + + for (const TimeRange &r : draw_range) { + DrawSubRect(painter, rect, scale, wave_range, *waveforms_, r); + } + } else { + AudioVisualWaveform::DrawWaveform(painter, rect, scale, *waveforms_, start_time); } -#else - AudioVisualWaveform::DrawWaveform(painter, rect, scale, waveforms_, start_time); -#endif } AudioVisualWaveform::Sample AudioWaveformCache::GetSummaryFromTime(const rational &start, const rational &length) const { -#ifdef AVW_USE_LIST - QMap sample; - - TimeRange acquire(start, start+length); - foreach (const TimeRangeWithWaveform &wv, waveforms_) { - if (wv.OverlapsWith(acquire)) { - TimeRange this_range = wv.Intersected(acquire); - auto sum = wv.waveform.GetSummaryFromTime(this_range.in() - wv.in(), this_range.length()); - sample.insert(this_range.in(), sum); - } - } - - AudioVisualWaveform::Sample result; - - for (auto it=sample.cbegin(); it!=sample.cend(); it++) { - result.insert(result.end(), it.value().begin(), it.value().end()); - } - - return result; -#else - return waveforms_.GetSummaryFromTime(start, length); -#endif + return waveforms_->GetSummaryFromTime(start, length); } rational AudioWaveformCache::length() const { -#ifdef AVW_USE_LIST - rational len = 0; - - foreach (const TimeRangeWithWaveform &wv, waveforms_) { - len = std::max(len, wv.out()); - } - - return len; -#else - return waveforms_.length(); -#endif + return waveforms_->length(); } void AudioWaveformCache::SetPassthrough(PlaybackCache *cache) { AudioWaveformCache *c = static_cast(cache); - waveforms_ = c->waveforms_; + for (const TimeRange &r : c->GetValidatedRanges()) { - Validate(r); + WaveformPassthrough t = r; + t.waveform = c->waveforms_; + passthroughs_.append(t); } + passthroughs_.append(c->passthroughs_); + SetParameters(c->GetParameters()); SetSavingEnabled(c->IsSavingEnabled()); } +void AudioWaveformCache::InvalidateEvent(const TimeRange& range) +{ + TimeRangeList::util_remove(&passthroughs_, range); + + super::InvalidateEvent(range); +} + } diff --git a/app/render/audiowaveformcache.h b/app/render/audiowaveformcache.h index a1a6dcfe5..feeeacb63 100644 --- a/app/render/audiowaveformcache.h +++ b/app/render/audiowaveformcache.h @@ -24,8 +24,6 @@ #include "audio/audiovisualwaveform.h" #include "playbackcache.h" -//#define AVW_USE_LIST - namespace olive { class AudioWaveformCache : public PlaybackCache @@ -40,7 +38,7 @@ public: void SetParameters(const AudioParams &p) { params_ = p; - waveforms_.set_channel_count(p.channel_count()); + waveforms_->set_channel_count(p.channel_count()); } void Draw(QPainter* painter, const QRect &rect, const double &scale, const rational &start_time) const; @@ -51,45 +49,28 @@ public: virtual void SetPassthrough(PlaybackCache *cache) override; +protected: + virtual void InvalidateEvent(const TimeRange& range); + private: -#ifdef AVW_USE_LIST - class TimeRangeWithWaveform : public TimeRange - { - public: - TimeRangeWithWaveform() = default; - TimeRangeWithWaveform(const TimeRange &r) : - TimeRange(r) - { - } + using WaveformPtr = std::shared_ptr; - void set_in(const rational& in) - { - waveform.TrimIn(in - this->in()); - TimeRange::set_in(in); - } - - void set_out(const rational& out) - { - waveform.Resize(out - this->in()); - TimeRange::set_out(out); - } - - void set_range(const rational& in, const rational& out) - { - waveform.TrimRange(in, out-in); - TimeRange::set_range(in, out); - } - - AudioVisualWaveform waveform; - }; - - QVector waveforms_; -#else - AudioVisualWaveform waveforms_; -#endif + WaveformPtr waveforms_; AudioParams params_; + class WaveformPassthrough : public TimeRange + { + public: + WaveformPassthrough(const TimeRange &r) : + TimeRange(r) + {} + + WaveformPtr waveform; + }; + + QVector passthroughs_; + }; } From fc579356d915677eb1e875de83852e54e22cce6b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:59:35 -0700 Subject: [PATCH 14/36] audiovisualwaveform: allow arbitrary offsets for waveforms --- app/audio/audiovisualwaveform.cpp | 44 ++++++++++++++++++++----------- app/audio/audiovisualwaveform.h | 4 +++ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index e13effd5c..065b781dd 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -90,6 +90,15 @@ void AudioVisualWaveform::OverwriteSamplesFromMipmap(const AudioVisualWaveform:: input_length = samples_length; } +void AudioVisualWaveform::ValidateVirtualStart(const rational &new_start) +{ + if (length_ == 0) { + virtual_start_ = new_start; + } else if (virtual_start_ > new_start) { + TrimIn(new_start - virtual_start_); + } +} + void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int sample_rate, const rational &start) { if (!channels_) { @@ -97,18 +106,12 @@ void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int samp return; } - // Old less optimized code. Keeping this around as a reference, but the below code is at least - // 10x faster so this shouldn't be used in production. - // - // size_t input_start, input_length; - // for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { - // OverwriteSamplesFromBuffer(samples, sample_rate, start, it->first.toDouble(), it->second, input_start, input_length); - // } + ValidateVirtualStart(start); // Process the largest mipmap directly for the samples auto current_mipmap = mipmapped_data_.rbegin(); size_t input_start, input_length; - OverwriteSamplesFromBuffer(samples, sample_rate, start, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length); + OverwriteSamplesFromBuffer(samples, sample_rate, start - virtual_start_, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length); while (true) { // For each smaller mipmap, we just process from the mipmap before it, making each one @@ -120,7 +123,7 @@ void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int samp } OverwriteSamplesFromMipmap(previous_mipmap->second, previous_mipmap->first.toDouble(), - input_start, input_length, start, current_mipmap->first.toDouble(), + input_start, input_length, start - virtual_start_, current_mipmap->first.toDouble(), current_mipmap->second); } @@ -130,6 +133,8 @@ void AudioVisualWaveform::OverwriteSamples(const SampleBuffer &samples, int samp void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const rational &dest, const rational& offset, const rational& length) { + ValidateVirtualStart(dest); + for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { rational rate = it->first; @@ -139,7 +144,7 @@ void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const r double rate_dbl = rate.toDouble(); // Get our destination sample - size_t our_start_index = time_to_samples(dest, rate_dbl); + size_t our_start_index = time_to_samples(dest - virtual_start_, rate_dbl); // Get our source sample size_t their_start_index = time_to_samples(offset, rate_dbl); @@ -172,6 +177,8 @@ void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const r void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational &length) { + ValidateVirtualStart(start); + for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { rational rate = it->first; @@ -180,7 +187,7 @@ void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational double rate_dbl = rate.toDouble(); // Get our destination sample - size_t our_start_index = time_to_samples(start, rate_dbl); + size_t our_start_index = time_to_samples(start - virtual_start_, rate_dbl); size_t our_length_index = time_to_samples(length, rate_dbl); size_t our_end_index = our_start_index + our_length_index; @@ -190,6 +197,8 @@ void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational memset(reinterpret_cast(our_arr.data()) + our_start_index * sizeof(SamplePerChannel), 0, our_length_index * sizeof(SamplePerChannel)); } + + length_ = qMax(length_, start + length); } void AudioVisualWaveform::TrimIn(rational length) @@ -198,6 +207,8 @@ void AudioVisualWaveform::TrimIn(rational length) return; } + virtual_start_ += length; + bool negative = (length < 0); if (negative) { length = -length; @@ -225,9 +236,9 @@ void AudioVisualWaveform::TrimIn(rational length) AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset) const { - AudioVisualWaveform mid = *this; + AudioVisualWaveform mid = *this; - mid.TrimIn(offset); + mid.TrimIn(offset - virtual_start_); return mid; } @@ -236,7 +247,7 @@ AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset, const ratio { AudioVisualWaveform mid = *this; - mid.TrimRange(offset, length); + mid.TrimRange(offset - virtual_start_, length); return mid; } @@ -273,7 +284,7 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration double rate_dbl = using_mipmap->first.toDouble(); - size_t start_sample = time_to_samples(start, rate_dbl); + size_t start_sample = time_to_samples(start - virtual_start_, rate_dbl); size_t sample_length = time_to_samples(length, rate_dbl); const Sample &mipmap_data = using_mipmap->second; @@ -421,7 +432,8 @@ void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, con double rate_dbl = rate.toDouble(); const Sample& arr = using_mipmap->second; - size_t start_sample_index = samples.time_to_samples(start_time, rate_dbl); + qDebug() << "drawing start time" << start_time << "-" << "vstart" << samples.virtual_start_ << "=" << (start_time-samples.virtual_start_); + size_t start_sample_index = samples.time_to_samples(start_time - samples.virtual_start_, rate_dbl); if (start_sample_index >= arr.size()) { return; diff --git a/app/audio/audiovisualwaveform.h b/app/audio/audiovisualwaveform.h index 8591ea11b..8f34961a1 100644 --- a/app/audio/audiovisualwaveform.h +++ b/app/audio/audiovisualwaveform.h @@ -123,6 +123,10 @@ private: std::map::const_iterator GetMipmapForScale(double scale) const; + void ValidateVirtualStart(const rational &new_start); + + rational virtual_start_; + int channels_; std::map mipmapped_data_; From af8c0942f0fafd55e9e7d77b851a5ccbce466744 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 27 Sep 2022 22:00:32 -0700 Subject: [PATCH 15/36] audiovisualwaveform: remove debug line --- app/audio/audiovisualwaveform.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 065b781dd..f6f0e4db4 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -432,7 +432,6 @@ void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, con double rate_dbl = rate.toDouble(); const Sample& arr = using_mipmap->second; - qDebug() << "drawing start time" << start_time << "-" << "vstart" << samples.virtual_start_ << "=" << (start_time-samples.virtual_start_); size_t start_sample_index = samples.time_to_samples(start_time - samples.virtual_start_, rate_dbl); if (start_sample_index >= arr.size()) { From fe159bdd73b19cfd918a9d0ec052317068773620 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 27 Sep 2022 22:06:05 -0700 Subject: [PATCH 16/36] rendermanager: use multithreading for waveforms --- app/render/rendermanager.cpp | 11 +++++++++-- app/render/rendermanager.h | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 19a0d8efc..7367c3c9f 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -55,7 +55,11 @@ RenderManager::RenderManager(QObject *parent) : video_thread_ = CreateThread(context_); dry_run_thread_ = CreateThread(); audio_thread_ = CreateThread(); - waveform_thread_ = CreateThread(); + + waveform_threads_.resize(QThread::idealThreadCount()); + for (size_t i=0; isetProperty("mode", params.mode); if (params.generate_waveforms) { - waveform_thread_->AddTicket(ticket); + size_t thread_index = last_waveform_thread_%waveform_threads_.size(); + RenderThread *thread = waveform_threads_[thread_index]; + thread->AddTicket(ticket); + last_waveform_thread_++; } else { audio_thread_->AddTicket(ticket); } diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index b33818b58..c1050464f 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -230,7 +230,9 @@ private: RenderThread *video_thread_; RenderThread *dry_run_thread_; RenderThread *audio_thread_; - RenderThread *waveform_thread_; + + std::vector waveform_threads_; + size_t last_waveform_thread_; std::list render_threads_; From 06c7dacf86ec2db2adaea31f56d21128ac09080f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 28 Sep 2022 08:45:03 -0700 Subject: [PATCH 17/36] audiowaveformcache: mark function override --- app/render/audiowaveformcache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/audiowaveformcache.h b/app/render/audiowaveformcache.h index feeeacb63..95a498d4f 100644 --- a/app/render/audiowaveformcache.h +++ b/app/render/audiowaveformcache.h @@ -50,7 +50,7 @@ public: virtual void SetPassthrough(PlaybackCache *cache) override; protected: - virtual void InvalidateEvent(const TimeRange& range); + virtual void InvalidateEvent(const TimeRange& range) override; private: using WaveformPtr = std::shared_ptr; From 74a1ec1479b4f69704c4345335447be28cc85a94 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 28 Sep 2022 17:57:09 -0700 Subject: [PATCH 18/36] multicam: implement clip splitting --- app/node/block/clip/clip.cpp | 10 ++ app/node/block/clip/clip.h | 3 + app/panel/multicam/multicampanel.h | 5 + app/widget/multicam/multicamwidget.cpp | 39 ++++++- app/widget/multicam/multicamwidget.h | 4 + .../timelinewidget/undo/timelineundosplit.cpp | 100 ++++++++++-------- .../timelinewidget/undo/timelineundosplit.h | 13 ++- app/window/mainwindow/mainwindow.cpp | 7 +- 8 files changed, 129 insertions(+), 52 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 787cf5442..13a62e8a1 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -541,4 +541,14 @@ TimeRange ClipBlock::media_range() const return InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); } +MultiCamNode *ClipBlock::FindMulticam() +{ + auto v = FindInputNodesConnectedToInput(NodeInput(this, kBufferIn)); + if (v.empty()) { + return nullptr; + } else { + return v.first(); + } +} + } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 5c84a1eca..15cadcfb7 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -24,6 +24,7 @@ #include "audio/audiovisualwaveform.h" #include "codec/decoder.h" #include "node/block/block.h" +#include "node/input/multicam/multicamnode.h" #include "node/output/track/track.h" namespace olive { @@ -197,6 +198,8 @@ public: SetStandardValue(kLoopModeInput, int(l)); } + MultiCamNode *FindMulticam(); + static const QString kBufferIn; static const QString kMediaInInput; static const QString kSpeedInput; diff --git a/app/panel/multicam/multicampanel.h b/app/panel/multicam/multicampanel.h index b8634bbfc..ac271aedb 100644 --- a/app/panel/multicam/multicampanel.h +++ b/app/panel/multicam/multicampanel.h @@ -17,6 +17,11 @@ public: widget_->SetMulticamNode(n); } + void SetClip(ClipBlock* clip) + { + widget_->SetClip(clip); + } + protected: virtual void Retranslate() override; diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 3ff424011..bca0c8db5 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -20,6 +20,7 @@ #include "multicamwidget.h" #include "widget/nodeparamview/nodeparamviewundo.h" +#include "widget/timelinewidget/undo/timelineundosplit.h" namespace olive { @@ -27,7 +28,8 @@ namespace olive { MulticamWidget::MulticamWidget(QWidget *parent) : super{new MulticamDisplay(), parent}, - node_(nullptr) + node_(nullptr), + clip_(nullptr) { auto_cacher()->SetMulticamMode(true); auto_cacher()->SetIgnoreCacheRequests(true); @@ -41,6 +43,11 @@ void MulticamWidget::SetMulticamNode(MultiCamNode *n) static_cast(display_widget())->SetMulticamNode(n); } +void MulticamWidget::SetClip(ClipBlock *clip) +{ + clip_ = clip; +} + RenderTicketPtr MulticamWidget::GetSingleFrame(const rational &t, bool dry) { if (node_) { @@ -73,8 +80,36 @@ void MulticamWidget::DisplayClicked(const QPoint &p) int r = click.y() / (height/multi); MultiUndoCommand *command = new MultiUndoCommand(); - command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(node_, node_->kCurrentInput)), node_->RowsColsToIndex(r, c, rows, cols))); + + const bool enable_split = true; + + MultiCamNode *cam = node_; + ClipBlock *clip = clip_; + + if (clip_ && enable_split && clip_->in() < GetTime() && clip_->out() > GetTime()) { + QVector blocks; + + blocks.append(clip_); + blocks.append(clip_->block_links()); + + auto split = new BlockSplitPreservingLinksCommand(blocks, {GetTime()}); + split->redo_now(); + command->add_child(split); + + clip = static_cast(split->GetSplit(clip_, 0)); + + cam = clip->FindMulticam(); + } + + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(cam, cam->kCurrentInput)), cam->RowsColsToIndex(r, c, rows, cols))); Core::instance()->undo_stack()->push(command); + + if (cam != node_) { + SetMulticamNode(cam); + } + if (clip != clip_) { + SetClip(clip); + } } } diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h index b80414c97..b32fce9e8 100644 --- a/app/widget/multicam/multicamwidget.h +++ b/app/widget/multicam/multicamwidget.h @@ -35,12 +35,16 @@ public: void SetMulticamNode(MultiCamNode *n); + void SetClip(ClipBlock *clip); + protected: virtual RenderTicketPtr GetSingleFrame(const rational &t, bool dry = false) override; private: MultiCamNode *node_; + ClipBlock *clip_; + private slots: void DisplayClicked(const QPoint &p); diff --git a/app/widget/timelinewidget/undo/timelineundosplit.cpp b/app/widget/timelinewidget/undo/timelineundosplit.cpp index da810f321..8386f6387 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.cpp +++ b/app/widget/timelinewidget/undo/timelineundosplit.cpp @@ -97,62 +97,68 @@ void BlockSplitCommand::undo() // // BlockSplitPreservingLinksCommand // -void BlockSplitPreservingLinksCommand::redo() +Block *BlockSplitPreservingLinksCommand::GetSplit(Block *original, int time_index) const { - if (commands_.isEmpty()) { - QVector< QVector > split_blocks(times_.size()); - - for (int i=0;i times_.at(i-1)); - - QVector splits(blocks_.size()); - - for (int j=0;jin() < time && b->out() > time) { - BlockSplitCommand* split_command = new BlockSplitCommand(b, time); - split_command->redo_now(); - splits.replace(j, split_command->new_block()); - commands_.append(split_command); - } else { - splits.replace(j, nullptr); - } - } - - split_blocks.replace(i, splits); + if (time_index >= 0 && time_index < times_.size()) { + int original_index = blocks_.indexOf(original); + if (original_index != -1) { + return splits_.at(time_index).at(original_index); } + } - // Now that we've determined all the splits, we can relink everything - for (int i=0;i times_.at(i-1)); - foreach (const QVector& split_list, split_blocks) { - NodeLinkCommand* blc = new NodeLinkCommand(split_list.at(i), split_list.at(j), true); - blc->redo_now(); - commands_.append(blc); - } - } + QVector splits(blocks_.size()); + + for (int j=0;jin() < time && b->out() > time) { + BlockSplitCommand* split_command = new BlockSplitCommand(b, time); + split_command->redo_now(); + splits.replace(j, split_command->new_block()); + commands_.append(split_command); + } else { + splits.replace(j, nullptr); } } - } else { - for (int i=0; iredo_now(); + + splits_.replace(i, splits); + } + + // Now that we've determined all the splits, we can relink everything + for (int i=0;i& split_list, splits_) { + NodeLinkCommand* blc = new NodeLinkCommand(split_list.at(i), split_list.at(j), true); + blc->redo_now(); + commands_.append(blc); + } + } } } } diff --git a/app/widget/timelinewidget/undo/timelineundosplit.h b/app/widget/timelinewidget/undo/timelineundosplit.h index 1fe9f9122..f7014d0e9 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.h +++ b/app/widget/timelinewidget/undo/timelineundosplit.h @@ -91,8 +91,17 @@ public: return blocks_.first()->project(); } + Block *GetSplit(Block *original, int time_index) const; + protected: - virtual void redo() override; + virtual void prepare() override; + + virtual void redo() override + { + for (int i=0; iredo_now(); + } + } virtual void undo() override { @@ -108,6 +117,8 @@ private: QVector commands_; + QVector< QVector > splits_; + }; class TrackSplitAtTimeCommand : public UndoCommand { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index a4aefc1b6..184b847f4 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -498,11 +498,12 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) if (PanelManager::instance()->CurrentlyFocused(false) == panel) { UpdateNodePanelContextFromTimelinePanel(panel); + ClipBlock *clip = nullptr; MultiCamNode *multicam = nullptr; for (Block *b : blocks) { - if (ClipBlock *c = dynamic_cast(b)) { - if ((multicam = dynamic_cast(c->GetConnectedOutput(c->kBufferIn)))) { + if ((clip = dynamic_cast(b))) { + if ((multicam = clip->FindMulticam())) { break; } } @@ -510,10 +511,12 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) if (multicam) { multicam_panel_->SetMulticamNode(multicam); + multicam_panel_->SetClip(clip); multicam_panel_->ConnectViewerNode(panel->GetConnectedViewer()); } else { multicam_panel_->ConnectViewerNode(nullptr); multicam_panel_->SetMulticamNode(nullptr); + multicam_panel_->SetClip(nullptr); } } } From 63163ba23e6669d9fe62a0c43665e9614d297210 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 28 Sep 2022 17:58:52 -0700 Subject: [PATCH 19/36] viewer: fix crash on some gizmos --- app/widget/viewer/viewerdisplay.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index bd1d8bc59..37a1ab08d 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -880,6 +880,7 @@ bool ViewerDisplayWidget::OnMouseMove(QMouseEvent *event) rational gizmo_time = GetGizmoTime(); NodeTraverser t; t.SetCacheVideoParams(gizmo_params_); + t.SetCacheAudioParams(gizmo_audio_params_); NodeValueRow row = t.GenerateRow(gizmos_, TimeRange(gizmo_time, gizmo_time + gizmo_params_.frame_rate_as_time_base())); draggable->DragStart(row, start.x(), start.y(), gizmo_time); @@ -1190,6 +1191,7 @@ void ViewerDisplayWidget::GenerateGizmoTransforms() { NodeTraverser gt; gt.SetCacheVideoParams(gizmo_params_); + gt.SetCacheAudioParams(gizmo_audio_params_); gizmo_draw_time_ = GenerateGizmoTime(); From b28495c7a650b068c558ed9ef88e5c8ca779a2fe Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 28 Sep 2022 19:56:18 -0700 Subject: [PATCH 20/36] nodeparamview: speed up node adding --- app/widget/nodeparamview/nodeparamview.cpp | 6 ++-- .../nodeparamviewconnectedlabel.cpp | 33 +++++++++++++------ .../nodeparamviewconnectedlabel.h | 2 ++ .../nodeparamview/nodeparamviewitem.cpp | 20 +++++------ .../nodeparamviewitemtitlebar.cpp | 10 +++--- .../nodeparamviewkeyframecontrol.h | 6 +++- .../nodeparamviewwidgetbridge.cpp | 28 ++++++++-------- .../nodeparamview/nodeparamviewwidgetbridge.h | 2 +- 8 files changed, 63 insertions(+), 44 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 5a6254bd5..b0d6f1618 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -76,7 +76,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : // Create contexts for three different types context_items_.resize(Track::kCount + 1); for (int i=0; isetVisible(false); connect(c, &NodeParamViewContext::AboutToDeleteItem, this, &NodeParamView::ItemAboutToBeRemoved, Qt::DirectConnection); @@ -245,8 +245,6 @@ void NodeParamView::DeselectNodes(const QVector &nodes) void NodeParamView::UpdateContexts() { - //TIME_THIS_FUNCTION; - bool changes_made = false; foreach (Node *ctx, current_contexts_) { @@ -735,7 +733,7 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) return; } - NodeParamViewItem* item = new NodeParamViewItem(n, IsGroupMode() ? kCheckBoxesOnNonConnected : kNoCheckBoxes, context); + NodeParamViewItem* item = new NodeParamViewItem(n, IsGroupMode() ? kCheckBoxesOnNonConnected : kNoCheckBoxes, context->GetDockArea()); connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::SelectNodeFromConnectedLink); diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp index 824452f4b..937037219 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp @@ -50,13 +50,13 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input, label_layout->setMargin(0); layout->addLayout(label_layout); - CollapseButton *collapse_btn = new CollapseButton(); + CollapseButton *collapse_btn = new CollapseButton(this); collapse_btn->setChecked(false); label_layout->addWidget(collapse_btn); - label_layout->addWidget(new QLabel(tr("Connected to"))); + label_layout->addWidget(new QLabel(tr("Connected to"), this)); - connected_to_lbl_ = new ClickableLabel(); + connected_to_lbl_ = new ClickableLabel(this); connected_to_lbl_->setCursor(Qt::PointingHandCursor); connected_to_lbl_->setContextMenuPolicy(Qt::CustomContextMenu); connect(connected_to_lbl_, &ClickableLabel::MouseClicked, this, &NodeParamViewConnectedLabel::ConnectionClicked); @@ -80,18 +80,23 @@ 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_); + // Creating the tree is expensive, hold off until the user specifically requests it + value_tree_ = nullptr; connect(collapse_btn, &CollapseButton::toggled, this, &NodeParamViewConnectedLabel::SetValueTreeVisible); } +void NodeParamViewConnectedLabel::CreateTree() +{ + // Set up table area + value_tree_ = new NodeValueTree(this); + layout()->addWidget(value_tree_); +} + void NodeParamViewConnectedLabel::SetTime(const rational &time) { time_ = time; - if (value_tree_->isVisible()) { + if (value_tree_ && value_tree_->isVisible()) { UpdateValueTree(); } } @@ -154,14 +159,22 @@ void NodeParamViewConnectedLabel::UpdateLabel() void NodeParamViewConnectedLabel::UpdateValueTree() { - value_tree_->SetNode(input_, time_); + if (value_tree_) { + value_tree_->SetNode(input_, time_); + } } void NodeParamViewConnectedLabel::SetValueTreeVisible(bool e) { - value_tree_->setVisible(e); + if (value_tree_) { + value_tree_->setVisible(e); + } if (e) { + if (!value_tree_) { + CreateTree(); + } + UpdateValueTree(); } } diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h index 183ff633b..9a7a81ee7 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h @@ -51,6 +51,8 @@ private: void UpdateValueTree(); + void CreateTree(); + ClickableLabel* connected_to_lbl_; NodeInput input_; diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 7b0e82fd3..a27cbe824 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -88,7 +88,7 @@ void NodeParamViewItem::RecreateBody() body_->deleteLater(); } - body_ = new NodeParamViewItemBody(node_, create_checkboxes_); + body_ = new NodeParamViewItemBody(node_, create_checkboxes_, this); connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); @@ -148,7 +148,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe if (n->InputIsArray(input)) { // Insert here - QWidget* array_widget = new QWidget(); + QWidget* array_widget = new QWidget(this); QGridLayout* array_layout = new QGridLayout(array_widget); array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0); @@ -160,7 +160,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe int arr_sz = 0; // Add one last add button for appending to the array - NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd); + NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd, this); connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked); array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn); @@ -186,7 +186,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const // Create optional checkbox if requested if (create_checkboxes_) { - ui_objects.optional_checkbox = new QCheckBox(); + ui_objects.optional_checkbox = new QCheckBox(this); connect(ui_objects.optional_checkbox, &QCheckBox::clicked, this, &NodeParamViewItemBody::OptionalCheckBoxClicked); layout->addWidget(ui_objects.optional_checkbox, row, kOptionalCheckBox); @@ -196,7 +196,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const } // Add descriptor label - ui_objects.main_label = new QLabel(); + ui_objects.main_label = new QLabel(this); // Create input label layout->addWidget(ui_objects.main_label, row, kLabelColumn); @@ -205,7 +205,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const if (element == -1) { // Create a collapse toggle for expanding/collapsing the array - CollapseButton* array_collapse_btn = new CollapseButton(); + CollapseButton* array_collapse_btn = new CollapseButton(this); // Default to collapsed array_collapse_btn->setChecked(false); @@ -220,8 +220,8 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const } else { - NodeParamViewArrayButton* insert_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd); - NodeParamViewArrayButton* remove_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kRemove); + NodeParamViewArrayButton* insert_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd, this); + NodeParamViewArrayButton* remove_element_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kRemove, this); layout->addWidget(insert_element_btn, row, kArrayInsertColumn); layout->addWidget(remove_element_btn, row, kArrayRemoveColumn); @@ -249,14 +249,14 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const if (node->IsInputConnectable(input)) { // Create clickable label used when an input is connected - ui_objects.connected_label = new NodeParamViewConnectedLabel(resolved); + ui_objects.connected_label = new NodeParamViewConnectedLabel(resolved, this); connect(ui_objects.connected_label, &NodeParamViewConnectedLabel::RequestSelectNode, this, &NodeParamViewItemBody::RequestSelectNode); layout->addWidget(ui_objects.connected_label, row, kWidgetStartColumn, 1, kKeyControlColumn - kWidgetStartColumn); } // Add keyframe control to this layout if parameter is keyframable if (node->IsInputKeyframable(input)) { - ui_objects.key_control = new NodeParamViewKeyframeControl(); + ui_objects.key_control = new NodeParamViewKeyframeControl(this); ui_objects.key_control->SetInput(resolved); layout->addWidget(ui_objects.key_control, row, kKeyControlColumn); connect(ui_objects.key_control, &NodeParamViewKeyframeControl::RequestSetTime, this, &NodeParamViewItemBody::RequestSetTime); diff --git a/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp index 0e9aa6b32..ea51f647b 100644 --- a/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp +++ b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp @@ -33,31 +33,31 @@ NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : { QHBoxLayout* layout = new QHBoxLayout(this); - collapse_btn_ = new CollapseButton(); + collapse_btn_ = new CollapseButton(this); connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged); layout->addWidget(collapse_btn_); - lbl_ = new QLabel(); + lbl_ = new QLabel(this); layout->addWidget(lbl_); // Place next buttons on the far side layout->addStretch(); - add_fx_btn_ = new QPushButton(); + add_fx_btn_ = new QPushButton(this); add_fx_btn_->setIcon(icon::AddEffect); add_fx_btn_->setFixedSize(add_fx_btn_->sizeHint().height(), add_fx_btn_->sizeHint().height()); add_fx_btn_->setVisible(false); layout->addWidget(add_fx_btn_); connect(add_fx_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::AddEffectButtonClicked); - pin_btn_ = new QPushButton(QStringLiteral("P")); + pin_btn_ = new QPushButton(QStringLiteral("P"), this); pin_btn_->setCheckable(true); pin_btn_->setFixedSize(pin_btn_->sizeHint().height(), pin_btn_->sizeHint().height()); pin_btn_->setVisible(false); layout->addWidget(pin_btn_); connect(pin_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled); - enabled_checkbox_ = new QCheckBox(); + enabled_checkbox_ = new QCheckBox(this); enabled_checkbox_->setVisible(false); layout->addWidget(enabled_checkbox_); connect(enabled_checkbox_, &QCheckBox::clicked, this, &NodeParamViewItemTitleBar::EnabledCheckBoxClicked); diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h index c11b206e0..0f8ffab7d 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.h @@ -33,7 +33,11 @@ class NodeParamViewKeyframeControl : public QWidget, public TimeTargetObject { Q_OBJECT public: - NodeParamViewKeyframeControl(bool right_align = true, QWidget* parent = nullptr); + NodeParamViewKeyframeControl(bool right_align, QWidget* parent = nullptr); + NodeParamViewKeyframeControl(QWidget* parent = nullptr) : + NodeParamViewKeyframeControl(true, parent) + { + } const NodeInput& GetConnectedInput() const { diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 9f0ae6ac8..dd33a7659 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -71,9 +71,11 @@ int GetSliderCount(NodeValue::Type type) void NodeParamViewWidgetBridge::CreateWidgets() { + QWidget *parent = dynamic_cast(this->parent()); + if (GetInnerInput().IsArray() && GetInnerInput().element() == -1) { - NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(GetInnerInput().node(), GetInnerInput().input()); + NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(GetInnerInput().node(), GetInnerInput().input(), parent); connect(w, &NodeParamViewArrayWidget::DoubleClicked, this, &NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked); widgets_.append(w); @@ -94,12 +96,12 @@ void NodeParamViewWidgetBridge::CreateWidgets() break; case NodeValue::kInt: { - CreateSliders(1); + CreateSliders(1, parent); break; } case NodeValue::kRational: { - CreateSliders(1); + CreateSliders(1, parent); break; } case NodeValue::kFloat: @@ -107,12 +109,12 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeValue::kVec3: case NodeValue::kVec4: { - CreateSliders(GetSliderCount(t)); + CreateSliders(GetSliderCount(t), parent); break; } case NodeValue::kCombo: { - QComboBox* combobox = new QComboBox(); + QComboBox* combobox = new QComboBox(parent); QStringList items = GetInnerInput().GetComboBoxStrings(); foreach (const QString& s, items) { @@ -125,21 +127,21 @@ void NodeParamViewWidgetBridge::CreateWidgets() } case NodeValue::kFile: { - FileField* file_field = new FileField(); + FileField* file_field = new FileField(parent); widgets_.append(file_field); connect(file_field, &FileField::FilenameChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; } case NodeValue::kColor: { - ColorButton* color_button = new ColorButton(GetInnerInput().node()->project()->color_manager()); + ColorButton* color_button = new ColorButton(GetInnerInput().node()->project()->color_manager(), parent); widgets_.append(color_button); connect(color_button, &ColorButton::ColorChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; } case NodeValue::kText: { - NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(); + NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(parent); widgets_.append(line_edit); connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback); connect(line_edit, &NodeParamViewTextEdit::RequestEditInViewer, this, &NodeParamViewWidgetBridge::RequestEditTextInViewer); @@ -147,21 +149,21 @@ void NodeParamViewWidgetBridge::CreateWidgets() } case NodeValue::kBoolean: { - QCheckBox* check_box = new QCheckBox(); + QCheckBox* check_box = new QCheckBox(parent); widgets_.append(check_box); connect(check_box, &QCheckBox::clicked, this, &NodeParamViewWidgetBridge::WidgetCallback); break; } case NodeValue::kFont: { - QFontComboBox* font_combobox = new QFontComboBox(); + QFontComboBox* font_combobox = new QFontComboBox(parent); widgets_.append(font_combobox); connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; } case NodeValue::kBezier: { - BezierWidget *bezier = new BezierWidget(); + BezierWidget *bezier = new BezierWidget(parent); widgets_.append(bezier); connect(bezier->x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); @@ -384,10 +386,10 @@ void NodeParamViewWidgetBridge::WidgetCallback() } template -void NodeParamViewWidgetBridge::CreateSliders(int count) +void NodeParamViewWidgetBridge::CreateSliders(int count, QWidget *parent) { for (int i=0;iSliderBase::SetDefaultValue(GetInnerInput().GetSplitDefaultValueForTrack(i)); fs->SetLadderElementCount(2); diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h index 98a71d5d7..772febbde 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.h +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.h @@ -75,7 +75,7 @@ private: void SetProperty(const QString &key, const QVariant &value); template - void CreateSliders(int count); + void CreateSliders(int count, QWidget *parent); void UpdateWidgetValues(); From 6e183fe4be11ff6f1ac577625490b35916b06161 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 29 Sep 2022 17:50:09 -0700 Subject: [PATCH 21/36] multicam: refactor to heavily optimize multicam monitor retrieval --- app/node/input/multicam/multicamnode.cpp | 97 +------------------- app/node/input/multicam/multicamnode.h | 7 -- app/node/traverser.h | 2 +- app/panel/multicam/multicampanel.cpp | 5 +- app/panel/multicam/multicampanel.h | 11 +-- app/panel/viewer/viewerbase.cpp | 24 ++--- app/panel/viewer/viewerbase.h | 21 ++++- app/render/previewautocacher.cpp | 16 ++-- app/render/previewautocacher.h | 6 +- app/render/rendermanager.cpp | 1 + app/render/rendermanager.h | 2 + app/render/renderprocessor.cpp | 24 +++++ app/render/renderprocessor.h | 2 + app/widget/multicam/multicamdisplay.cpp | 111 ++++++++++++++++++++++- app/widget/multicam/multicamdisplay.h | 10 ++ app/widget/multicam/multicamwidget.cpp | 51 ++++++++--- app/widget/multicam/multicamwidget.h | 11 ++- app/widget/viewer/viewer.cpp | 27 ++++-- app/widget/viewer/viewer.h | 12 ++- app/widget/viewer/viewerdisplay.cpp | 2 + app/widget/viewer/viewerdisplay.h | 5 + app/widget/viewer/viewerqueue.h | 6 +- app/widget/viewer/viewersizer.cpp | 2 + app/widget/viewer/viewersizer.h | 2 +- app/window/mainwindow/mainwindow.cpp | 5 + 25 files changed, 300 insertions(+), 162 deletions(-) diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index b468af862..5984e4b98 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -17,8 +17,6 @@ MultiCamNode::MultiCamNode() AddInput(kSourcesInput, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); SetInputProperty(kSourcesInput, QStringLiteral("arraystart"), 1); - - monitor_ = false; } QString MultiCamNode::Name() const @@ -43,7 +41,7 @@ QString MultiCamNode::Description() const Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, const TimeRange &r) const { - if (input == kSourcesInput && !monitor_) { + if (input == kSourcesInput) { Node::ActiveElements a; a.add(GetCurrentSource()); return a; @@ -52,98 +50,11 @@ Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, } } -QString dblToGlsl(double d) -{ - return QString::number(d, 'f'); -} - -ShaderCode MultiCamNode::GetShaderCode(const ShaderRequest &id) const -{ - QStringList pieces = id.id.split(','); - int rows = pieces.at(0).toInt(); - int cols = pieces.at(1).toInt(); - int multiplier = std::max(cols, rows); - - QStringList shader; - - shader.append(QStringLiteral("in vec2 ove_texcoord;")); - shader.append(QStringLiteral("out vec4 frag_color;")); - - for (int x=0;x 0) { - shader.append(QStringLiteral(" else")); - } - if (x == cols-1) { - shader.append(QStringLiteral(" {")); - } else { - shader.append(QStringLiteral(" if (ove_texcoord.x < %1) {").arg(dblToGlsl(double(x+1)/double(multiplier)))); - } - - for (int y=0;y 0) { - shader.append(QStringLiteral(" else")); - } - if (y == rows-1) { - shader.append(QStringLiteral(" {")); - } else { - shader.append(QStringLiteral(" if (ove_texcoord.y < %1) {").arg(dblToGlsl(double(y+1)/double(multiplier)))); - } - QString input = QStringLiteral("tex_%1_%2").arg(QString::number(y), QString::number(x)); - shader.append(QStringLiteral(" vec2 coord = vec2((ove_texcoord.x+%1)*%2, (ove_texcoord.y+%3)*%4);").arg( - dblToGlsl( - double(x)/double(multiplier)), - dblToGlsl(multiplier), - dblToGlsl( - double(y)/double(multiplier)), - dblToGlsl(multiplier) - )); - shader.append(QStringLiteral(" if (%1_enabled && coord.x >= 0.0 && coord.x < 1.0 && coord.y >= 0.0 && coord.y < 1.0) {").arg(input)); - shader.append(QStringLiteral(" frag_color = texture(%1, coord);").arg(input)); - shader.append(QStringLiteral(" } else {")); - shader.append(QStringLiteral(" discard;")); - shader.append(QStringLiteral(" }")); - shader.append(QStringLiteral(" }")); - } - - shader.append(QStringLiteral(" }")); - } - - shader.append(QStringLiteral("}")); - - return ShaderCode(shader.join('\n')); -} - void MultiCamNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - if (!monitor_) { - NodeValueArray arr = value[kSourcesInput].toArray(); - if (!arr.empty()) { - table->Push(arr.begin()->second); - } - } else { - NodeValueArray arr = value[kSourcesInput].toArray(); - - int rows, cols; - GetRowsAndColumns(arr.size(), &rows, &cols); - - ShaderJob job; - - job.SetShaderID(QStringLiteral("%1,%2").arg(QString::number(rows), QString::number(cols))); - - for (int i=0; iPush(NodeValue::kTexture, Texture::Job(globals.vparams(), job), this); + NodeValueArray arr = value[kSourcesInput].toArray(); + if (!arr.empty()) { + table->Push(arr.begin()->second); } } diff --git a/app/node/input/multicam/multicamnode.h b/app/node/input/multicam/multicamnode.h index d3e57734b..56a9ed858 100644 --- a/app/node/input/multicam/multicamnode.h +++ b/app/node/input/multicam/multicamnode.h @@ -20,8 +20,6 @@ public: virtual ActiveElements GetActiveElementsAtTime(const QString &input, const TimeRange &r) const override; - virtual ShaderCode GetShaderCode(const ShaderRequest &id) const override; - virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; virtual void Retranslate() override; @@ -29,8 +27,6 @@ public: static const QString kCurrentInput; static const QString kSourcesInput; - void SetMonitorMode(bool e) { monitor_ = e; } - int GetCurrentSource() const { return GetStandardValue(kCurrentInput).toInt(); @@ -54,9 +50,6 @@ public: return col + row * total_cols; } -private: - bool monitor_; - }; } diff --git a/app/node/traverser.h b/app/node/traverser.h index e34a708b5..4f2041d57 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -42,7 +42,7 @@ public: NodeValueTable GenerateTable(const Node *n, const TimeRange &range, const Node *next_node = nullptr); - NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range); + virtual NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range); NodeValueRow GenerateRow(NodeValueDatabase *database, const Node *node, const TimeRange &range); NodeValueRow GenerateRow(const Node *node, const TimeRange &range); diff --git a/app/panel/multicam/multicampanel.cpp b/app/panel/multicam/multicampanel.cpp index 322b51458..7218e43e3 100644 --- a/app/panel/multicam/multicampanel.cpp +++ b/app/panel/multicam/multicampanel.cpp @@ -2,13 +2,12 @@ namespace olive { -#define super ViewerPanelBase +#define super TimeBasedPanel MulticamPanel::MulticamPanel(QWidget *parent) : super(QStringLiteral("MultiCamPanel"), parent) { - widget_ = new MulticamWidget(); - SetViewerWidget(widget_); + SetTimeBasedWidget(new MulticamWidget()); Retranslate(); } diff --git a/app/panel/multicam/multicampanel.h b/app/panel/multicam/multicampanel.h index ac271aedb..86137f2bd 100644 --- a/app/panel/multicam/multicampanel.h +++ b/app/panel/multicam/multicampanel.h @@ -6,28 +6,27 @@ namespace olive { -class MulticamPanel : public ViewerPanelBase +class MulticamPanel : public TimeBasedPanel { Q_OBJECT public: MulticamPanel(QWidget* parent = nullptr); + MulticamWidget *GetMulticamWidget() const { return static_cast(GetTimeBasedWidget()); } + void SetMulticamNode(MultiCamNode *n) { - widget_->SetMulticamNode(n); + GetMulticamWidget()->SetMulticamNode(n); } void SetClip(ClipBlock* clip) { - widget_->SetClip(clip); + GetMulticamWidget()->SetClip(clip); } protected: virtual void Retranslate() override; -private: - MulticamWidget *widget_; - }; } diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp index 6c90506b6..5d1bf7b95 100644 --- a/app/panel/viewer/viewerbase.cpp +++ b/app/panel/viewer/viewerbase.cpp @@ -24,35 +24,37 @@ namespace olive { +#define super TimeBasedPanel + ViewerPanelBase::ViewerPanelBase(const QString& object_name, QWidget *parent) : - TimeBasedPanel(object_name, parent) + super(object_name, parent) { connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &ViewerPanelBase::FocusedPanelChanged); } void ViewerPanelBase::PlayPause() { - static_cast(GetTimeBasedWidget())->TogglePlayPause(); + GetViewerWidget()->TogglePlayPause(); } void ViewerPanelBase::PlayInToOut() { - static_cast(GetTimeBasedWidget())->Play(true); + GetViewerWidget()->Play(true); } void ViewerPanelBase::ShuttleLeft() { - static_cast(GetTimeBasedWidget())->ShuttleLeft(); + GetViewerWidget()->ShuttleLeft(); } void ViewerPanelBase::ShuttleStop() { - static_cast(GetTimeBasedWidget())->ShuttleStop(); + GetViewerWidget()->ShuttleStop(); } void ViewerPanelBase::ShuttleRight() { - static_cast(GetTimeBasedWidget())->ShuttleRight(); + GetViewerWidget()->ShuttleRight(); } void ViewerPanelBase::ConnectTimeBasedPanel(TimeBasedPanel *panel) @@ -75,22 +77,22 @@ void ViewerPanelBase::DisconnectTimeBasedPanel(TimeBasedPanel *panel) void ViewerPanelBase::SetFullScreen(QScreen *screen) { - static_cast(GetTimeBasedWidget())->SetFullScreen(screen); + GetViewerWidget()->SetFullScreen(screen); } void ViewerPanelBase::SetGizmos(Node *node) { - static_cast(GetTimeBasedWidget())->SetGizmos(node); + GetViewerWidget()->SetGizmos(node); } void ViewerPanelBase::CacheEntireSequence() { - static_cast(GetTimeBasedWidget())->CacheEntireSequence(); + GetViewerWidget()->CacheEntireSequence(); } void ViewerPanelBase::CacheSequenceInOut() { - static_cast(GetTimeBasedWidget())->CacheSequenceInOut(); + GetViewerWidget()->CacheSequenceInOut(); } void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw) @@ -104,7 +106,7 @@ void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw) void ViewerPanelBase::FocusedPanelChanged(PanelWidget *panel) { - auto vw = static_cast(GetTimeBasedWidget()); + auto vw = GetViewerWidget(); if (vw->IsPlaying() && panel != this) { vw->Pause(); } diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index 1ca7c06ff..feaf4045e 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -33,6 +33,11 @@ class ViewerPanelBase : public TimeBasedPanel public: ViewerPanelBase(const QString& object_name, QWidget* parent = nullptr); + ViewerWidget *GetViewerWidget() const + { + return static_cast(GetTimeBasedWidget()); + } + virtual void PlayPause() override; virtual void PlayInToOut() override; @@ -54,12 +59,22 @@ public: ColorManager *GetColorManager() { - return static_cast(GetTimeBasedWidget())->color_manager(); + return GetViewerWidget()->color_manager(); } void UpdateTextureFromNode() { - static_cast(GetTimeBasedWidget())->UpdateTextureFromNode(); + GetViewerWidget()->UpdateTextureFromNode(); + } + + void AddPlaybackDevice(ViewerDisplayWidget *vw) + { + GetViewerWidget()->AddPlaybackDevice(vw); + } + + void SetMulticamNode(MultiCamNode *n) + { + GetViewerWidget()->SetMulticamNode(n); } public slots: @@ -71,7 +86,7 @@ public slots: void RequestStartEditingText() { - static_cast(GetTimeBasedWidget())->RequestStartEditingText(); + GetViewerWidget()->RequestStartEditingText(); } signals: diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 4bb23ee7f..226ebe69a 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -43,7 +43,7 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) : pause_renders_(false), single_frame_render_(nullptr), display_color_processor_(nullptr), - multicam_mode_(false), + multicam_(nullptr), ignore_cache_requests_(false) { // Set defaults @@ -216,6 +216,7 @@ void PreviewAutoCacher::VideoRendered() QVector tickets = video_immediate_passthroughs_.take(watcher); foreach (RenderTicketPtr t, tickets) { if (watcher->HasResult()) { + t->setProperty("multicam_output", watcher->GetTicket()->property("multicam_output")); t->Finish(watcher->Get()); } else { t->Finish(); @@ -288,13 +289,6 @@ void PreviewAutoCacher::AddNode(Node *node) // Copy node Node* copy = node->copy(); - // Fairly hacky way of getting multicam nodes to produce a monitor rather than a single source - if (multicam_mode_) { - if (MultiCamNode *m = dynamic_cast(copy)) { - m->SetMonitorMode(true); - } - } - // Add to project copy->setParent(&copied_project_); @@ -742,6 +736,9 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& // Allow using cached images for this render job rvp.use_cache = true; + // Multicam + rvp.multicam = static_cast(copy_map_.value(multicam_)); + watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp)); return watcher; @@ -848,6 +845,9 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) video_cache_data_.clear(); audio_cache_data_.clear(); + // Clear multicam reference + multicam_ = nullptr; + // Disconnect signals for future node additions/deletions NodeGraph* graph = viewer_node_->parent(); diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 9f68bb9e5..af8fff0bb 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -90,7 +90,8 @@ public: void SetRendersPaused(bool e); - void SetMulticamMode(bool e) { multicam_mode_ = e; } + void SetMulticamNode(MultiCamNode *n) { multicam_ = n; } + void SetIgnoreCacheRequests(bool e) { ignore_cache_requests_ = e; } public slots: @@ -221,7 +222,8 @@ private: ColorProcessorPtr display_color_processor_; - bool multicam_mode_; + MultiCamNode *multicam_; + bool ignore_cache_requests_; private slots: diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 19a0d8efc..13966a892 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -111,6 +111,7 @@ RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams ¶ms) ticket->setProperty("cache", params.cache_dir); ticket->setProperty("cachetimebase", QVariant::fromValue(params.cache_timebase)); ticket->setProperty("cacheid", QVariant::fromValue(params.cache_id)); + ticket->setProperty("multicam", Node::PtrToValue(params.multicam)); if (params.return_type == ReturnType::kNull) { dry_run_thread_->AddTicket(ticket); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index b33818b58..da52892c4 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -117,6 +117,7 @@ public: force_size = QSize(0, 0); force_channel_count = 0; mode = m; + multicam = nullptr; } void AddCache(FrameHashCache *cache) @@ -134,6 +135,7 @@ public: bool use_cache; ReturnType return_type; RenderMode::Mode mode; + MultiCamNode *multicam; QString cache_dir; rational cache_timebase; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index bd5702f1f..2b42f9e67 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -294,6 +294,30 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c return dec; } +NodeValueDatabase RenderProcessor::GenerateDatabase(const Node *node, const TimeRange &range) +{ + NodeValueDatabase db = super::GenerateDatabase(node, range); + + if (const MultiCamNode *multicam = dynamic_cast(node)) { + if (Node::ValueToPtr(ticket_->property("multicam")) == multicam) { + int sz = multicam->InputArraySize(multicam->kSourcesInput); + NodeValueTableArray arr; + QVector multicam_tex(sz); + for (int i=0; ikSourcesInput, i, range); + + NodeValue val = GenerateRowValueElement(multicam, multicam->kSourcesInput, i, &arr.at(i), range); + ResolveJobs(val); + + multicam_tex[i] = val.toTexture(); + } + ticket_->setProperty("multicam_output", QVariant::fromValue(multicam_tex)); + } + } + + return db; +} + void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache *decoder_cache, ShaderCache *shader_cache) { RenderProcessor p(ticket, render_ctx, decoder_cache, shader_cache); diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 3eb477b14..b3dd29f59 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -32,6 +32,8 @@ namespace olive { class RenderProcessor : public NodeTraverser { public: + virtual NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range) override; + static void Process(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache); struct RenderedWaveform { diff --git a/app/widget/multicam/multicamdisplay.cpp b/app/widget/multicam/multicamdisplay.cpp index a5b79a67b..9fce75e68 100644 --- a/app/widget/multicam/multicamdisplay.cpp +++ b/app/widget/multicam/multicamdisplay.cpp @@ -26,7 +26,9 @@ namespace olive { MulticamDisplay::MulticamDisplay(QWidget *parent) : super(parent), - node_(nullptr) + node_(nullptr), + rows_(0), + cols_(0) { } @@ -55,6 +57,113 @@ void MulticamDisplay::OnPaint() } } +void MulticamDisplay::OnDestroy() +{ + shader_ = QVariant(); +} + +TexturePtr MulticamDisplay::LoadCustomTextureFromFrame(const QVariant &v) +{ + if (v.canConvert >()) { + QVector tex = v.value >(); + + TexturePtr main = renderer()->CreateTexture(this->GetViewportParams()); + + int rows, cols; + MultiCamNode::GetRowsAndColumns(tex.size(), &rows, &cols); + + if (shader_.isNull() || rows_ != rows || cols_ != cols) { + if (!shader_.isNull()) { + renderer()->DestroyNativeShader(shader_); + } + + shader_ = renderer()->CreateNativeShader(ShaderCode(GenerateShaderCode(rows, cols))); + + rows_ = rows; + cols_ = cols; + } + + ShaderJob job; + + for (int i=0; iBlitToTexture(shader_, job, main.get()); + + return main; + } else { + return super::LoadCustomTextureFromFrame(v); + } +} + +QString dblToGlsl(double d) +{ + return QString::number(d, 'f'); +} + +QString MulticamDisplay::GenerateShaderCode(int rows, int cols) +{ + int multiplier = std::max(cols, rows); + + QStringList shader; + + shader.append(QStringLiteral("in vec2 ove_texcoord;")); + shader.append(QStringLiteral("out vec4 frag_color;")); + + for (int x=0;x 0) { + shader.append(QStringLiteral(" else")); + } + if (x == cols-1) { + shader.append(QStringLiteral(" {")); + } else { + shader.append(QStringLiteral(" if (ove_texcoord.x < %1) {").arg(dblToGlsl(double(x+1)/double(multiplier)))); + } + + for (int y=0;y 0) { + shader.append(QStringLiteral(" else")); + } + if (y == rows-1) { + shader.append(QStringLiteral(" {")); + } else { + shader.append(QStringLiteral(" if (ove_texcoord.y < %1) {").arg(dblToGlsl(double(y+1)/double(multiplier)))); + } + QString input = QStringLiteral("tex_%1_%2").arg(QString::number(y), QString::number(x)); + shader.append(QStringLiteral(" vec2 coord = vec2((ove_texcoord.x+%1)*%2, (ove_texcoord.y+%3)*%4);").arg( + dblToGlsl( - double(x)/double(multiplier)), + dblToGlsl(multiplier), + dblToGlsl( - double(y)/double(multiplier)), + dblToGlsl(multiplier) + )); + shader.append(QStringLiteral(" if (%1_enabled && coord.x >= 0.0 && coord.x < 1.0 && coord.y >= 0.0 && coord.y < 1.0) {").arg(input)); + shader.append(QStringLiteral(" frag_color = texture(%1, coord);").arg(input)); + shader.append(QStringLiteral(" } else {")); + shader.append(QStringLiteral(" discard;")); + shader.append(QStringLiteral(" }")); + shader.append(QStringLiteral(" }")); + } + + shader.append(QStringLiteral(" }")); + } + + shader.append(QStringLiteral("}")); + + return shader.join('\n'); +} + void MulticamDisplay::SetMulticamNode(MultiCamNode *n) { node_ = n; diff --git a/app/widget/multicam/multicamdisplay.h b/app/widget/multicam/multicamdisplay.h index 2898142d1..4d926f5f1 100644 --- a/app/widget/multicam/multicamdisplay.h +++ b/app/widget/multicam/multicamdisplay.h @@ -37,9 +37,19 @@ public: protected: virtual void OnPaint() override; + virtual void OnDestroy() override; + + virtual TexturePtr LoadCustomTextureFromFrame(const QVariant &v) override; + private: + static QString GenerateShaderCode(int rows, int cols); + MultiCamNode *node_; + QVariant shader_; + int rows_; + int cols_; + }; } diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index bca0c8db5..6e8e6f22a 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -20,27 +20,41 @@ #include "multicamwidget.h" #include "widget/nodeparamview/nodeparamviewundo.h" +#include "widget/timeruler/timeruler.h" #include "widget/timelinewidget/undo/timelineundosplit.h" namespace olive { -#define super ViewerWidget +#define super TimeBasedWidget MulticamWidget::MulticamWidget(QWidget *parent) : - super{new MulticamDisplay(), parent}, + super{false, false, parent}, node_(nullptr), clip_(nullptr) { - auto_cacher()->SetMulticamMode(true); - auto_cacher()->SetIgnoreCacheRequests(true); + auto layout = new QVBoxLayout(this); - connect(display_widget(), &ViewerDisplayWidget::DragStarted, this, &MulticamWidget::DisplayClicked); + sizer_ = new ViewerSizer(this); + layout->addWidget(sizer_); + + display_ = new MulticamDisplay(this); + display_->SetShowWidgetBackground(true); + connect(display_, &ViewerDisplayWidget::DragStarted, this, &MulticamWidget::DisplayClicked); + + connect(sizer_, &ViewerSizer::RequestScale, display_, &ViewerDisplayWidget::SetMatrixZoom); + connect(sizer_, &ViewerSizer::RequestTranslate, display_, &ViewerDisplayWidget::SetMatrixTranslate); + connect(display_, &ViewerDisplayWidget::HandDragMoved, sizer_, &ViewerSizer::HandDragMove); + sizer_->SetWidget(display_); + + layout->addWidget(this->ruler()); + layout->addWidget(this->scrollbar()); } void MulticamWidget::SetMulticamNode(MultiCamNode *n) { node_ = n; - static_cast(display_widget())->SetMulticamNode(n); + display_->SetMulticamNode(n); + display_->update(); } void MulticamWidget::SetClip(ClipBlock *clip) @@ -48,13 +62,20 @@ void MulticamWidget::SetClip(ClipBlock *clip) clip_ = clip; } -RenderTicketPtr MulticamWidget::GetSingleFrame(const rational &t, bool dry) +void MulticamWidget::ConnectNodeEvent(ViewerOutput *n) { - if (node_) { - return auto_cacher()->GetSingleFrame(node_, t, dry); - } else { - return super::GetSingleFrame(t, dry); - } + connect(n, &ViewerOutput::SizeChanged, sizer_, &ViewerSizer::SetChildSize); + connect(n, &ViewerOutput::PixelAspectChanged, sizer_, &ViewerSizer::SetPixelAspectRatio); + + VideoParams vp = n->GetVideoParams(); + sizer_->SetChildSize(vp.width(), vp.height()); + sizer_->SetPixelAspectRatio(vp.pixel_aspect_ratio()); +} + +void MulticamWidget::DisconnectNodeEvent(ViewerOutput *n) +{ + disconnect(n, &ViewerOutput::SizeChanged, sizer_, &ViewerSizer::SetChildSize); + disconnect(n, &ViewerOutput::PixelAspectChanged, sizer_, &ViewerSizer::SetPixelAspectRatio); } void MulticamWidget::DisplayClicked(const QPoint &p) @@ -63,9 +84,9 @@ void MulticamWidget::DisplayClicked(const QPoint &p) return; } - QPointF click = display_widget()->ScreenToScenePoint(p); - int width = display_widget()->GetVideoParams().width(); - int height = display_widget()->GetVideoParams().height(); + QPointF click = display_->ScreenToScenePoint(p); + int width = display_->GetVideoParams().width(); + int height = display_->GetVideoParams().height(); if (click.x() < 0 || click.y() < 0 || click.x() >= width || click.y() >= height) { return; diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h index b32fce9e8..36380861b 100644 --- a/app/widget/multicam/multicamwidget.h +++ b/app/widget/multicam/multicamwidget.h @@ -27,20 +27,27 @@ namespace olive { -class MulticamWidget : public ViewerWidget +class MulticamWidget : public TimeBasedWidget { Q_OBJECT public: explicit MulticamWidget(QWidget *parent = nullptr); + MulticamDisplay *GetDisplayWidget() const { return display_; } + void SetMulticamNode(MultiCamNode *n); void SetClip(ClipBlock *clip); protected: - virtual RenderTicketPtr GetSingleFrame(const rational &t, bool dry = false) override; + virtual void ConnectNodeEvent(ViewerOutput *n) override; + virtual void DisconnectNodeEvent(ViewerOutput *n) override; private: + ViewerSizer *sizer_; + + MulticamDisplay *display_; + MultiCamNode *node_; ClipBlock *clip_; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index bdf28a4de..f8515c145 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -44,6 +44,7 @@ #include "viewerpreventsleep.h" #include "widget/audiomonitor/audiomonitor.h" #include "widget/menu/menu.h" +#include "widget/multicam/multicamdisplay.h" #include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/timelinewidget/tool/add.h" #include "widget/timeruler/timeruler.h" @@ -82,7 +83,6 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) : // Create main OpenGL-based view and sizer sizer_ = new ViewerSizer(); - sizer_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout->addWidget(sizer_); display_widget_ = display; @@ -260,7 +260,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) CloseAudioProcessor(); audio_scrub_watchers_.clear(); - SetDisplayImage(QVariant()); + SetDisplayImage(nullptr); ruler()->SetPlaybackCache(nullptr); @@ -998,10 +998,18 @@ bool ViewerWidget::ViewerMightBeAStill() return GetConnectedNode() && GetConnectedNode()->GetConnectedTextureOutput() && GetConnectedNode()->GetVideoLength().isNull(); } -void ViewerWidget::SetDisplayImage(QVariant frame) +void ViewerWidget::SetDisplayImage(RenderTicketPtr ticket) { foreach (ViewerDisplayWidget *dw, playback_devices_) { - dw->SetImage(frame); + QVariant push; + if (ticket) { + if (dynamic_cast(dw)) { + push = ticket->property("multicam_output"); + } else { + push = ticket->Get(); + } + } + dw->SetImage(push); } } @@ -1160,7 +1168,7 @@ void ViewerWidget::RendererGeneratedFrame() } } - SetDisplayImage(ticket->Get()); + SetDisplayImage(ticket->GetTicket()); } } @@ -1182,7 +1190,14 @@ void ViewerWidget::RendererGeneratedFrameForQueue() rational ts = watcher->property("time").value(); foreach (ViewerDisplayWidget *dw, playback_devices_) { - dw->queue()->AppendTimewise({ts, frame}, playback_speed_); + QVariant push; + if (dynamic_cast(dw)) { + push = watcher->GetTicket()->property("multicam_output"); + } else { + push = frame; + } + + dw->queue()->AppendTimewise({ts, push}, playback_speed_); } if (prequeuing_video_) { diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 400853652..a47c0d6d2 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -104,6 +104,16 @@ public: PreviewAutoCacher *GetCacher() const { return auto_cacher_; } + void AddPlaybackDevice(ViewerDisplayWidget *vw) + { + playback_devices_.push_back(vw); + } + + void SetMulticamNode(MultiCamNode *n) + { + auto_cacher()->SetMulticamNode(n); + } + public slots: void Play(bool in_to_out_only); @@ -219,7 +229,7 @@ private: bool ViewerMightBeAStill(); - void SetDisplayImage(QVariant frame); + void SetDisplayImage(RenderTicketPtr ticket); RenderTicketWatcher *RequestNextFrameForQueue(bool increment = true); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 37a1ab08d..e7ec99fb5 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -398,6 +398,8 @@ void ViewerDisplayWidget::OnPaint() } else if (TexturePtr texture = load_frame_.value()) { // This is a GPU texture, switch to it directly texture_ = texture; + } else { + texture_ = LoadCustomTextureFromFrame(load_frame_); } emit TextureChanged(texture_); diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 077ea6023..f2e89102f 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -245,6 +245,11 @@ protected: return TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base()); } + virtual TexturePtr LoadCustomTextureFromFrame(const QVariant &v) + { + return nullptr; + } + protected slots: /** * @brief Paint function to display the texture (received in SetTexture()) on screen. diff --git a/app/widget/viewer/viewerqueue.h b/app/widget/viewer/viewerqueue.h index f8117eac9..053d8f013 100644 --- a/app/widget/viewer/viewerqueue.h +++ b/app/widget/viewer/viewerqueue.h @@ -27,12 +27,14 @@ namespace olive { -struct ViewerPlaybackFrame { +struct ViewerPlaybackFrame +{ rational timestamp; QVariant frame; }; -class ViewerQueue : public std::list { +class ViewerQueue : public std::list +{ public: ViewerQueue() = default; diff --git a/app/widget/viewer/viewersizer.cpp b/app/widget/viewer/viewersizer.cpp index 7041da533..d8ae52b84 100644 --- a/app/widget/viewer/viewersizer.cpp +++ b/app/widget/viewer/viewersizer.cpp @@ -39,6 +39,8 @@ ViewerSizer::ViewerSizer(QWidget *parent) : vert_scrollbar_ = new QScrollBar(Qt::Vertical, this); vert_scrollbar_->setVisible(false); connect(vert_scrollbar_, &QScrollBar::valueChanged, this, &ViewerSizer::ScrollBarMoved); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } void ViewerSizer::SetWidget(QWidget *widget) diff --git a/app/widget/viewer/viewersizer.h b/app/widget/viewer/viewersizer.h index 7acb76965..b4afe5015 100644 --- a/app/widget/viewer/viewersizer.h +++ b/app/widget/viewer/viewersizer.h @@ -51,6 +51,7 @@ public: */ void SetWidget(QWidget* widget); +public slots: /** * @brief Set resolution to use * @@ -70,7 +71,6 @@ public: */ void SetZoom(int percent); -public slots: void HandDragMove(int x, int y); signals: diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 184b847f4..346650d0d 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -125,6 +125,8 @@ MainWindow::MainWindow(QWidget *parent) : sequence_viewer_panel_->ConnectTimeBasedPanel(curve_panel_); sequence_viewer_panel_->ConnectTimeBasedPanel(multicam_panel_); + sequence_viewer_panel_->AddPlaybackDevice(multicam_panel_->GetMulticamWidget()->GetDisplayWidget()); + scope_panel_->SetViewerPanel(sequence_viewer_panel_); UpdateTitle(); @@ -510,10 +512,12 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) } if (multicam) { + sequence_viewer_panel_->SetMulticamNode(multicam); multicam_panel_->SetMulticamNode(multicam); multicam_panel_->SetClip(clip); multicam_panel_->ConnectViewerNode(panel->GetConnectedViewer()); } else { + sequence_viewer_panel_->SetMulticamNode(nullptr); multicam_panel_->ConnectViewerNode(nullptr); multicam_panel_->SetMulticamNode(nullptr); multicam_panel_->SetClip(nullptr); @@ -678,6 +682,7 @@ void MainWindow::RemoveProjectPanel(ProjectPanel *panel) void MainWindow::TimelineFocused(ViewerOutput* viewer) { sequence_viewer_panel_->ConnectViewerNode(viewer); + multicam_panel_->ConnectViewerNode(viewer); param_panel_->ConnectViewerNode(viewer); curve_panel_->ConnectViewerNode(viewer); } From 640e6741f43f633dc5ec75c09d7b33a169efe5cb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 29 Sep 2022 19:45:38 -0700 Subject: [PATCH 22/36] multicam: prioritize current clip under playhead --- app/panel/timeline/timeline.h | 5 ++ app/widget/multicam/multicamwidget.cpp | 1 - app/widget/viewer/viewer.cpp | 8 ++ app/widget/viewer/viewer.h | 5 +- app/window/mainwindow/mainwindow.cpp | 108 ++++++++++++++++--------- app/window/mainwindow/mainwindow.h | 10 +++ 6 files changed, 96 insertions(+), 41 deletions(-) diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 611fd275c..5ee3743a9 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -107,6 +107,11 @@ public: return timeline_widget()->GetSelectedBlocks(); } + Sequence *GetSequence() const + { + return dynamic_cast(GetConnectedViewer()); + } + protected: virtual void Retranslate() override; diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 6e8e6f22a..3ac92c20e 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -54,7 +54,6 @@ void MulticamWidget::SetMulticamNode(MultiCamNode *n) { node_ = n; display_->SetMulticamNode(n); - display_->update(); } void MulticamWidget::SetClip(ClipBlock *clip) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index f8515c145..7382a2b3f 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -799,6 +799,14 @@ void ViewerWidget::UpdateTextureFromNode() } } +void ViewerWidget::SetMulticamNode(MultiCamNode *n) +{ + auto_cacher()->SetMulticamNode(n); + if (!IsPlaying()) { + UpdateTextureFromNode(); + } +} + void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) { Q_ASSERT(speed != 0); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index a47c0d6d2..5acff6cc3 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -109,10 +109,7 @@ public: playback_devices_.push_back(vw); } - void SetMulticamNode(MultiCamNode *n) - { - auto_cacher()->SetMulticamNode(n); - } + void SetMulticamNode(MultiCamNode *n); public slots: void Play(bool in_to_out_only); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 346650d0d..256f6abab 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -38,7 +38,8 @@ namespace olive { MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent) + QMainWindow(parent), + last_multicam_panel_(nullptr) { // Resizes main window to desktop geometry on startup. Fixes the following issues: // * Qt on Windows has a bug that "de-maximizes" the window when widgets are added, resizing the @@ -106,25 +107,17 @@ MainWindow::MainWindow(QWidget *parent) : connect(param_panel_, &ParamPanel::SelectedNodesChanged, node_panel_, &NodePanel::Select); // Connect time signals together - connect(multicam_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime); - connect(multicam_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime); - connect(multicam_panel_, &SequenceViewerPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); - connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime); - connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime); - connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime); - connect(param_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); - connect(param_panel_, &ParamPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime); - connect(param_panel_, &ParamPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime); - connect(curve_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); - connect(curve_panel_, &ParamPanel::TimeChanged, param_panel_, &NodeTablePanel::SetTime); - connect(curve_panel_, &ParamPanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime); - - connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); + AddMainTimePanel(multicam_panel_); + AddMainTimePanel(curve_panel_); + AddMainTimePanel(param_panel_); + AddMainTimePanel(sequence_viewer_panel_); sequence_viewer_panel_->ConnectTimeBasedPanel(param_panel_); sequence_viewer_panel_->ConnectTimeBasedPanel(curve_panel_); sequence_viewer_panel_->ConnectTimeBasedPanel(multicam_panel_); + connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); + sequence_viewer_panel_->AddPlaybackDevice(multicam_panel_->GetMulticamWidget()->GetDisplayWidget()); scope_panel_->SetViewerPanel(sequence_viewer_panel_); @@ -500,29 +493,58 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) if (PanelManager::instance()->CurrentlyFocused(false) == panel) { UpdateNodePanelContextFromTimelinePanel(panel); - ClipBlock *clip = nullptr; - MultiCamNode *multicam = nullptr; + last_multicam_panel_ = panel; + UpdateMulticamNode(); + } +} - for (Block *b : blocks) { +void MainWindow::UpdateMulticamNode() +{ + TimelinePanel *panel = last_multicam_panel_; + if (!panel) { + return; + } + + ClipBlock *clip = nullptr; + MultiCamNode *multicam = nullptr; + + for (Block *b : panel->GetSelectedBlocks()) { + if (b->range().Contains(panel->GetTime())) { if ((clip = dynamic_cast(b))) { if ((multicam = clip->FindMulticam())) { break; } } } + } - if (multicam) { - sequence_viewer_panel_->SetMulticamNode(multicam); - multicam_panel_->SetMulticamNode(multicam); - multicam_panel_->SetClip(clip); - multicam_panel_->ConnectViewerNode(panel->GetConnectedViewer()); - } else { - sequence_viewer_panel_->SetMulticamNode(nullptr); - multicam_panel_->ConnectViewerNode(nullptr); - multicam_panel_->SetMulticamNode(nullptr); - multicam_panel_->SetClip(nullptr); + if (!multicam && panel->GetSequence()) { + const QVector &tracks = panel->GetSequence()->GetTracks(); + for (Track *t : tracks) { + if (t->IsLocked()) { + continue; + } + + Block *b = t->NearestBlockBeforeOrAt(panel->GetTime()); + if ((clip = dynamic_cast(b))) { + if ((multicam = clip->FindMulticam())) { + break; + } + } } } + + if (multicam) { + multicam_panel_->SetMulticamNode(multicam); + sequence_viewer_panel_->SetMulticamNode(multicam); + multicam_panel_->SetClip(clip); + multicam_panel_->ConnectViewerNode(panel->GetConnectedViewer()); + } else { + multicam_panel_->ConnectViewerNode(nullptr); + sequence_viewer_panel_->SetMulticamNode(nullptr); + multicam_panel_->SetMulticamNode(nullptr); + multicam_panel_->SetClip(nullptr); + } } void MainWindow::ShowWelcomeDialog() @@ -624,23 +646,34 @@ void MainWindow::FloatingPanelCloseRequested() panel->deleteLater(); } +void MainWindow::AddMainTimePanel(TimeBasedPanel *p) +{ + main_time_panels_.append(p); + connect(p, &TimeBasedPanel::TimeChanged, this, &MainWindow::UpdateMainTimePanels); +} + +void MainWindow::UpdateMainTimePanels(const rational &r) +{ + for (TimeBasedPanel *p : main_time_panels_) { + if (p != sender()) { + p->SetTime(r); + } + } + + UpdateMulticamNode(); +} + TimelinePanel* MainWindow::AppendTimelinePanel() { TimelinePanel* panel = AppendPanelInternal(timeline_panels_); connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::TimelineCloseRequested); - connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTime); - connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTime); - connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); - connect(panel, &TimelinePanel::TimeChanged, multicam_panel_, &MulticamPanel::SetTime); connect(panel, &TimelinePanel::RequestCaptureStart, sequence_viewer_panel_, &SequenceViewerPanel::StartCapture); connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged); connect(panel, &TimelinePanel::RevealViewerInProject, this, &MainWindow::RevealViewerInProject); connect(panel, &TimelinePanel::RevealViewerInFootageViewer, this, &MainWindow::RevealViewerInFootageViewer); - connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); - connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); - connect(multicam_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTime); - connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTime); + + AddMainTimePanel(panel); sequence_viewer_panel_->ConnectTimeBasedPanel(panel); @@ -660,6 +693,9 @@ ProjectPanel *MainWindow::AppendProjectPanel() void MainWindow::RemoveTimelinePanel(TimelinePanel *panel) { // Stop showing this timeline in the viewer + if (last_multicam_panel_ == panel) { + last_multicam_panel_ = nullptr; + } TimelineFocused(nullptr); panel->ConnectViewerNode(nullptr); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 892d2ca54..7b0e8979c 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -147,6 +147,10 @@ private: void SelectFootageForProjectPanel(const QVector &e, ProjectPanel *p); + void UpdateMulticamNode(); + + void AddMainTimePanel(TimeBasedPanel *p); + QByteArray premaximized_state_; // Standard panels @@ -174,6 +178,10 @@ private: bool first_show_; + QVector main_time_panels_; + + TimelinePanel *last_multicam_panel_; + private slots: void FocusedPanelChanged(PanelWidget* panel); @@ -204,6 +212,8 @@ private slots: void RevealViewerInProject(ViewerOutput *r); void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range); + void UpdateMainTimePanels(const rational &r); + }; } From 2d0af6f335bc93b4eef46670684abb544d844dbc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 29 Sep 2022 20:19:32 -0700 Subject: [PATCH 23/36] track: fix audio regression --- app/node/output/track/track.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 3d242077a..5587457a5 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -688,7 +688,7 @@ void Track::ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &glob // Copy samples into destination buffer for (int i=0; i Date: Fri, 30 Sep 2022 16:01:36 -0700 Subject: [PATCH 24/36] track: ensure copies are within bounds --- app/node/output/track/track.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 5587457a5..c0e72cfd7 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -684,7 +684,7 @@ void Track::ProcessAudioTrack(const NodeValueRow &value, const NodeGlobals &glob } } - qint64 copy_length = qMin(max_dest_sz, qint64(samples_from_this_block.sample_count())); + qint64 copy_length = qMin(max_dest_sz, qint64(samples_from_this_block.sample_count() - destination_offset)); // Copy samples into destination buffer for (int i=0; i Date: Sat, 1 Oct 2022 09:57:00 -0700 Subject: [PATCH 25/36] viewer: fix issue with waveform on footage --- app/node/output/viewer/viewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 8c9456626..4dfce5b64 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -389,7 +389,7 @@ Node::ValueHint ViewerOutput::GetConnectedSampleValueHint() void ViewerOutput::ConnectedToPreviewEvent() { - if (Node *connected = GetConnectedOutput(kSamplesInput)) { + if (Node *connected = this->GetConnectedSampleOutput()) { TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength())); TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); for (const TimeRange &r : invalid) { From 087325e4b20d477dca2b1b0b868f687e276e9a96 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 1 Oct 2022 09:57:28 -0700 Subject: [PATCH 26/36] audiowaveformview: fixed bug with waveform update signal --- app/widget/viewer/audiowaveformview.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index 9b5403942..10a4c85eb 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -53,7 +53,7 @@ void AudioWaveformView::SetViewer(ViewerOutput *playback) pool_.clear(); pool_.waitForDone(); - disconnect(playback_, &ViewerOutput::ConnectedWaveformChanged, this, static_cast(&AudioWaveformView::update)); + disconnect(playback_, &ViewerOutput::ConnectedWaveformChanged, viewport(), static_cast(&QWidget::update)); SetTimebase(0); } @@ -61,7 +61,7 @@ void AudioWaveformView::SetViewer(ViewerOutput *playback) playback_ = playback; if (playback_) { - connect(playback_, &ViewerOutput::ConnectedWaveformChanged, this, static_cast(&AudioWaveformView::update)); + connect(playback_, &ViewerOutput::ConnectedWaveformChanged, viewport(), static_cast(&QWidget::update)); SetTimebase(playback_->GetAudioParams().sample_rate_as_time_base()); } From df97df635d587e2938d3907f26365297e501083c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 1 Oct 2022 13:04:51 -0700 Subject: [PATCH 27/36] timeline: implement nest function Also includes some improved cache/request code --- app/core.cpp | 4 +- app/core.h | 6 +- app/node/block/clip/clip.cpp | 4 +- app/node/output/track/track.cpp | 27 +++++ app/node/output/track/track.h | 5 + app/node/output/viewer/viewer.cpp | 10 +- app/panel/timeline/timeline.h | 5 + app/render/playbackcache.cpp | 7 ++ app/render/playbackcache.h | 18 +++- app/render/previewautocacher.cpp | 54 ++++++---- app/render/previewautocacher.h | 2 +- app/widget/menu/menushared.cpp | 2 +- app/widget/timelinewidget/timelinewidget.cpp | 102 ++++++++++++++++++- app/widget/timelinewidget/timelinewidget.h | 2 + app/widget/timelinewidget/tool/import.cpp | 21 ++-- app/widget/timelinewidget/tool/import.h | 6 +- 16 files changed, 230 insertions(+), 45 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 0a90e92d4..1dd1317c2 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1490,7 +1490,7 @@ bool Core::LabelNodes(const QVector &nodes, MultiUndoCommand *parent) return false; } -Sequence *Core::CreateNewSequenceForProject(Project* project) const +Sequence *Core::CreateNewSequenceForProject(const QString &format, Project* project) { Sequence* new_sequence = new Sequence(); @@ -1498,7 +1498,7 @@ Sequence *Core::CreateNewSequenceForProject(Project* project) const int sequence_number = 1; QString sequence_name; do { - sequence_name = tr("Sequence %1").arg(sequence_number); + sequence_name = format.arg(sequence_number); sequence_number++; } while (project->root()->ChildExistsWithName(sequence_name)); new_sequence->SetLabel(sequence_name); diff --git a/app/core.h b/app/core.h index a2ded04db..0d9d8df0d 100644 --- a/app/core.h +++ b/app/core.h @@ -255,7 +255,11 @@ public: /** * @brief Create a new sequence named appropriately for the active project */ - Sequence* CreateNewSequenceForProject(Project *project) const; + static Sequence* CreateNewSequenceForProject(const QString &format, Project *project); + static Sequence* CreateNewSequenceForProject(Project *project) + { + return CreateNewSequenceForProject(tr("Sequence %1"), project); + } /** * @brief Opens a project from the recently opened list diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 787cf5442..fd0ed6113 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -232,7 +232,7 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range) { TimeRange thumb_range = range.Intersected(max_range); if (GetAdjustedThumbnailRange(&thumb_range)) { - emit connected->thumbnail_cache()->Request(thumb_range); + connected->thumbnail_cache()->Request(thumb_range); } } @@ -296,7 +296,7 @@ void ClipBlock::RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_ } if (request) { - emit cache->Request(r); + cache->Request(r); } } diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index b2d3775f0..53f874366 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -404,6 +404,33 @@ QVector Track::BlocksAtTimeRange(const TimeRange &range) const return list; } +bool Track::IsRangeFree(const TimeRange &range) const +{ + Block *b = NearestBlockBeforeOrAt(range.in()); + if (!b) { + // No block here, assume track is empty here + return true; + } + + if (!dynamic_cast(b)) { + // There's a block at or around the start point that isn't a gap, range is not free + return false; + } + + while ((b = b->next())) { + if (b->in() >= range.out()) { + // This block is after the range, no longer relevant + break; + } else if (!dynamic_cast(b)) { + // Found a block in this range, range is not free + return false; + } + } + + // If we get here, we couldn't find anything in the way of this range + return true; +} + void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) { TimeRange limited; diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 4994bf73c..34bb82f35 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -338,6 +338,11 @@ public: */ QVector BlocksAtTimeRange(const TimeRange& range) const; + /* + * @brief Returns whether a time range is empty or only has a gap + */ + bool IsRangeFree(const TimeRange &range) const; + const QVector &Blocks() const { return blocks_; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 4dfce5b64..34ec101d6 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -224,16 +224,16 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, if (Node *connected = GetConnectedOutput(from, element)) { if (from == kTextureInput) { - //emit connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + //connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); if (autocache_input_video_) { TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength())); - emit connected->video_frame_cache()->Request(range.Intersected(max_range)); + connected->video_frame_cache()->Request(range.Intersected(max_range)); } } else if (from == kSamplesInput) { TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength())); - emit connected->waveform_cache()->Request(range.Intersected(max_range)); + connected->waveform_cache()->Request(range.Intersected(max_range)); if (autocache_input_audio_) { - emit connected->audio_playback_cache()->Request(range.Intersected(max_range)); + connected->audio_playback_cache()->Request(range.Intersected(max_range)); } } } @@ -393,7 +393,7 @@ void ViewerOutput::ConnectedToPreviewEvent() TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength())); TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); for (const TimeRange &r : invalid) { - emit connected->waveform_cache()->Request(r); + connected->waveform_cache()->Request(r); } } } diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 611fd275c..ae41d8c88 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -98,6 +98,11 @@ public: timeline_widget()->ShowSpeedDurationDialogForSelectedClips(); } + void NestSelectedClips() + { + timeline_widget()->NestSelectedClips(); + } + void InsertFootageAtPlayhead(const QVector &footage); void OverwriteFootageAtPlayhead(const QVector &footage); diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index bf606e153..7cc89d473 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -223,6 +223,13 @@ void PlaybackCache::InvalidateAll() Invalidate(TimeRange(0, RATIONAL_MAX)); } +void PlaybackCache::Request(const TimeRange &r) +{ + requested_.insert(r); + + emit Requested(r); +} + void PlaybackCache::Validate(const TimeRange &r, bool signal) { validated_.insert(r); diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index 3485fdcb4..8f483d1c5 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -98,15 +98,29 @@ public: const QVector &GetPassthroughs() const { return passthroughs_; } + void ClearRequestRange(const olive::TimeRange &r) + { + requested_.remove(r); + } + + void ResignalRequests() + { + for (const TimeRange &r : requested_) { + emit Requested(r); + } + } + public slots: void InvalidateAll(); + void Request(const olive::TimeRange &r); + signals: void Invalidated(const olive::TimeRange& r); void Validated(const olive::TimeRange& r); - void Request(const olive::TimeRange& r); + void Requested(const olive::TimeRange& r); void CancelAll(); @@ -124,6 +138,8 @@ protected: private: TimeRangeList validated_; + TimeRangeList requested_; + QUuid uuid_; bool saving_enabled_; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 5a9a5a21e..827e4d910 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -100,6 +100,8 @@ void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range) { PlaybackCache *cache = static_cast(sender()); + cache->ClearRequestRange(range); + VideoInvalidatedFromNode(cache, range); } @@ -107,6 +109,8 @@ void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range) { PlaybackCache *cache = static_cast(sender()); + cache->ClearRequestRange(range); + AudioInvalidatedFromNode(cache, range); } @@ -240,7 +244,10 @@ void PreviewAutoCacher::VideoRendered() void PreviewAutoCacher::ProcessUpdateQueue() { // Iterate everything that happened to the graph and do the same thing on our end - foreach (const QueuedJob& job, graph_update_queue_) { + while (!graph_update_queue_.empty()) { + QueuedJob job = graph_update_queue_.front(); + graph_update_queue_.pop_front(); + switch (job.type) { case QueuedJob::kNodeAdded: AddNode(job.node); @@ -262,7 +269,6 @@ void PreviewAutoCacher::ProcessUpdateQueue() break; } } - graph_update_queue_.clear(); // Indicate that we have synchronized to this point, which is compared with the graph change // time to see if our copied graph is up to date @@ -368,22 +374,22 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) void PreviewAutoCacher::ConnectToNodeCache(Node *node) { connect(node->video_frame_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::VideoInvalidatedFromCache); connect(node->thumbnail_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::VideoInvalidatedFromCache); connect(node->audio_playback_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::AudioInvalidatedFromCache); connect(node->waveform_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::AudioInvalidatedFromCache); @@ -396,27 +402,32 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node) &PlaybackCache::CancelAll, this, &PreviewAutoCacher::CancelForCache); + + node->video_frame_cache()->ResignalRequests(); + node->thumbnail_cache()->ResignalRequests(); + node->audio_playback_cache()->ResignalRequests(); + node->waveform_cache()->ResignalRequests(); } void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) { disconnect(node->video_frame_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::VideoInvalidatedFromCache); disconnect(node->thumbnail_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::VideoInvalidatedFromCache); disconnect(node->audio_playback_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::AudioInvalidatedFromCache); disconnect(node->waveform_cache(), - &PlaybackCache::Request, + &PlaybackCache::Requested, this, &PreviewAutoCacher::AudioInvalidatedFromCache); @@ -466,6 +477,8 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR using_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base(); } + cache->ClearRequestRange(range); + TimeRangeListFrameIterator iterator({range}, using_tb); pending_video_jobs_.push_back({node, cache, range, iterator}); video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_); @@ -475,6 +488,9 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeRange &range) { Node *node = cache->parent(); + + cache->ClearRequestRange(range); + pending_audio_jobs_.push_back({node, cache, range}); audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_); TryRender(); @@ -486,6 +502,8 @@ void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const Tim // want to dedicate all our rendering power to realtime feedback for the user //CancelVideoTasks(node); + cache->ClearRequestRange(range); + // If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames if (!NodeInputDragger::IsInputBeingDragged()) { StartCachingVideoRange(cache, range); @@ -498,6 +516,8 @@ void PreviewAutoCacher::AudioInvalidatedFromNode(PlaybackCache *cache, const Tim // cancelled, so some areas may end up unrendered forever // ClearAudioQueue(); + cache->ClearRequestRange(range); + // If we're auto-caching audio or require realtime waveforms, we'll have to render this StartCachingAudioRange(cache, range); } @@ -553,37 +573,37 @@ void PreviewAutoCacher::SetRendersPaused(bool e) void PreviewAutoCacher::NodeAdded(Node *node) { - graph_update_queue_.append({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); + graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); UpdateGraphChangeValue(); } void PreviewAutoCacher::NodeRemoved(Node *node) { - graph_update_queue_.append({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr}); + graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr}); UpdateGraphChangeValue(); } void PreviewAutoCacher::EdgeAdded(Node *output, const NodeInput &input) { - graph_update_queue_.append({QueuedJob::kEdgeAdded, nullptr, input, output}); + graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output}); UpdateGraphChangeValue(); } void PreviewAutoCacher::EdgeRemoved(Node *output, const NodeInput &input) { - graph_update_queue_.append({QueuedJob::kEdgeRemoved, nullptr, input, output}); + graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output}); UpdateGraphChangeValue(); } void PreviewAutoCacher::ValueChanged(const NodeInput &input) { - graph_update_queue_.append({QueuedJob::kValueChanged, nullptr, input, nullptr}); + graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr}); UpdateGraphChangeValue(); } void PreviewAutoCacher::ValueHintChanged(const NodeInput &input) { - graph_update_queue_.append({QueuedJob::kValueHintChanged, nullptr, input, nullptr}); + graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr}); UpdateGraphChangeValue(); } @@ -591,7 +611,7 @@ void PreviewAutoCacher::TryRender() { delayed_requeue_timer_.stop(); - if (!graph_update_queue_.isEmpty()) { + if (!graph_update_queue_.empty()) { // Check if we have jobs running in other threads that shouldn't be interrupted right now // NOTE: We don't check for downloads because, while they run in another thread, they don't // require any access to the graph and therefore don't risk race conditions. diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 05e253851..2d5bfebd7 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -160,7 +160,7 @@ private: Project copied_project_; - QVector graph_update_queue_; + std::list graph_update_queue_; QHash copy_map_; QHash graph_map_; ViewerOutput* copied_viewer_node_; diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 6332046ba..1ff70f954 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -293,7 +293,7 @@ void MenuShared::EnableDisableTriggered() void MenuShared::NestTriggered() { - qDebug() << "FIXME: Stub"; + PanelManager::instance()->MostRecentlyFocused()->NestSelectedClips(); } void MenuShared::DefaultTransitionTriggered() diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index f54792aef..e53d77f0d 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -539,12 +539,16 @@ void TimelineWidget::DecreaseTrackHeight() void TimelineWidget::InsertFootageAtPlayhead(const QVector& footage) { - import_tool_->PlaceAt(footage, GetTime(), true); + auto command = new MultiUndoCommand(); + import_tool_->PlaceAt(footage, GetTime(), true, command); + Core::instance()->undo_stack()->push(command); } void TimelineWidget::OverwriteFootageAtPlayhead(const QVector &footage) { - import_tool_->PlaceAt(footage, GetTime(), false); + auto command = new MultiUndoCommand(); + import_tool_->PlaceAt(footage, GetTime(), false, command); + Core::instance()->undo_stack()->push(command); } void TimelineWidget::ToggleLinksOnSelected() @@ -788,13 +792,14 @@ void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange task.Start(); MultiUndoCommand *import_command = task.GetCommand(); - Core::instance()->undo_stack()->pushIfHasChildren(import_command); if (task.GetImportedFootage().empty()) { qCritical() << "Failed to import recorded audio file" << filename; } else { - import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, track.index()); + import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, import_command, track.index()); } + + Core::instance()->undo_stack()->pushIfHasChildren(import_command); } void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord) @@ -839,6 +844,95 @@ void TimelineWidget::AddTentativeSubtitleTrack() } } +void TimelineWidget::NestSelectedClips() +{ + if (!GetConnectedNode()) { + return; + } + + QVector blocks = this->selected_blocks_; + if (blocks.empty()) { + return; + } + + QVector tracks(blocks.size()); + QVector times(blocks.size()); + QVector track_offset(Track::kCount, INT_MAX); + rational start_time = RATIONAL_MAX; + rational end_time = RATIONAL_MIN; + for (int i=0; itrack()->ToReference();; + tracks[i] = tf; + times[i] = b->range(); + + int &to = track_offset[tf.type()]; + to = std::min(to, tf.index()); + + start_time = std::min(start_time, b->in()); + end_time = std::max(end_time, b->out()); + } + + auto move_to_nest_command = new MultiUndoCommand(); + + // Remove blocks from this sequence + ReplaceBlocksWithGaps(blocks, false, move_to_nest_command); + + // Create new sequence + Project *project = this->GetConnectedNode()->project(); + Sequence *nest = Core::CreateNewSequenceForProject(tr("Nested Sequence %1"), project); + nest->SetVideoParams(GetConnectedNode()->GetVideoParams()); + nest->SetAudioParams(GetConnectedNode()->GetAudioParams()); + move_to_nest_command->add_child(new NodeAddCommand(project, nest)); + + // Add to same folder + move_to_nest_command->add_child(new FolderAddChild(this->GetConnectedNode()->folder(), nest)); + + // Place blocks in new sequence + for (int i=0; iadd_child(new TrackPlaceBlockCommand(nest->track_list(track.type()), + track.index() - track_offset.at(track.type()), + b, range.in() - start_time)); + } + + // Do this command now, because we later do checks and actions that rely on these having been done + move_to_nest_command->redo_now(); + + auto meta_command = new MultiUndoCommand(); + meta_command->add_child(move_to_nest_command); + + // Find first free track index + bool empty = false; + int index = -1; + while (!empty) { + index++; + empty = true; + for (int i=0; itrack_list(static_cast(i)); + if (index < list->GetTrackCount() && !list->GetTrackAt(index)->IsRangeFree(TimeRange(start_time, end_time))) { + empty = false; + break; + } + } + } + + // Place new sequence in this sequence + import_tool_->PlaceAt({nest}, start_time, false, meta_command, index); + + Core::instance()->undo_stack()->push(meta_command); +} + void TimelineWidget::ClearTentativeSubtitleTrack() { if (subtitle_show_command_) { diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index d952c5419..dd081af0f 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -112,6 +112,8 @@ public: void AddTentativeSubtitleTrack(); + void NestSelectedClips(); + /** * @brief Timelines should always be connected to sequences */ diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 02dec714d..caa0037ed 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -183,7 +183,9 @@ void ImportTool::DragLeave(QDragLeaveEvent* event) void ImportTool::DragDrop(TimelineViewMouseEvent *event) { if (!dragged_footage_.isEmpty()) { - DropGhosts(event->GetModifiers() & Qt::ControlModifier); + auto command = new MultiUndoCommand(); + DropGhosts(event->GetModifiers() & Qt::ControlModifier, command); + Core::instance()->undo_stack()->pushIfHasChildren(command); event->accept(); } else { @@ -191,7 +193,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event) } } -void ImportTool::PlaceAt(const QVector &footage, const rational &start, bool insert, int track_offset) +void ImportTool::PlaceAt(const QVector &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset) { DraggedFootageData refs; @@ -199,10 +201,10 @@ void ImportTool::PlaceAt(const QVector &footage, const rational refs.append({f, f->GetEnabledStreamsAsReferences()}); } - PlaceAt(refs, start, insert, track_offset); + PlaceAt(refs, start, insert, command, track_offset); } -void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, int track_offset) +void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset) { dragged_footage_ = footage; @@ -211,7 +213,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star } PrepGhosts(start, track_offset); - DropGhosts(insert); + DropGhosts(insert, command); } void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData &sorted, const rational& dest_tb, const int& track_start) @@ -292,9 +294,9 @@ void ImportTool::PrepGhosts(const rational& frame, const int& track_index) } } -void ImportTool::DropGhosts(bool insert) +void ImportTool::DropGhosts(bool insert, MultiUndoCommand *parent_command) { - MultiUndoCommand* command = new MultiUndoCommand(); + auto command = new MultiUndoCommand(); if (MultiUndoCommand *c = parent()->TakeSubtitleSectionCommand()) { command->add_child(c); @@ -500,7 +502,10 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new OpenSequenceCommand(sequence)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + // Do command now because RequestInvalidatedFromConnected relies on track type, which will be + // "none" before this command is done because it won't be connected to any track + command->redo_now(); + parent_command->add_child(command); while (!imported_clips.empty()) { imported_clips.front()->RequestInvalidatedFromConnected(); diff --git a/app/widget/timelinewidget/tool/import.h b/app/widget/timelinewidget/tool/import.h index 565d8f0ce..765fd9d04 100644 --- a/app/widget/timelinewidget/tool/import.h +++ b/app/widget/timelinewidget/tool/import.h @@ -37,8 +37,8 @@ public: using DraggedFootageData = QVector > >; - void PlaceAt(const QVector &footage, const rational& start, bool insert, int track_offset = 0); - void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, int track_offset = 0); + void PlaceAt(const QVector &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0); + void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0); enum DropWithoutSequenceBehavior { kDWSAsk, @@ -52,7 +52,7 @@ private: void PrepGhosts(const rational &frame, const int &track_index); - void DropGhosts(bool insert); + void DropGhosts(bool insert, MultiUndoCommand *parent_command); TimelineViewGhostItem* CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track); From 14bf515d3f00e6bb76c8fb94686261c4758d254a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 1 Oct 2022 16:57:46 -0700 Subject: [PATCH 28/36] multicam: various improvements --- app/panel/multicam/multicampanel.h | 8 +-- app/panel/viewer/viewerbase.cpp | 1 + app/panel/viewer/viewerbase.h | 11 +++- app/widget/multicam/multicamwidget.cpp | 77 ++++++++++++++++---------- app/widget/multicam/multicamwidget.h | 2 + app/widget/viewer/viewer.cpp | 63 ++++++++++++++++++--- app/widget/viewer/viewer.h | 23 +++++++- app/window/mainwindow/mainwindow.cpp | 63 ++------------------- app/window/mainwindow/mainwindow.h | 4 -- 9 files changed, 142 insertions(+), 110 deletions(-) diff --git a/app/panel/multicam/multicampanel.h b/app/panel/multicam/multicampanel.h index 86137f2bd..ffa9616d0 100644 --- a/app/panel/multicam/multicampanel.h +++ b/app/panel/multicam/multicampanel.h @@ -14,13 +14,11 @@ public: MulticamWidget *GetMulticamWidget() const { return static_cast(GetTimeBasedWidget()); } - void SetMulticamNode(MultiCamNode *n) +public slots: + void SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip) { + ConnectViewerNode(viewer); GetMulticamWidget()->SetMulticamNode(n); - } - - void SetClip(ClipBlock* clip) - { GetMulticamWidget()->SetClip(clip); } diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp index 5d1bf7b95..ac575699e 100644 --- a/app/panel/viewer/viewerbase.cpp +++ b/app/panel/viewer/viewerbase.cpp @@ -100,6 +100,7 @@ void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw) connect(vw, &ViewerWidget::TextureChanged, this, &ViewerPanelBase::TextureChanged); connect(vw, &ViewerWidget::ColorProcessorChanged, this, &ViewerPanelBase::ColorProcessorChanged); connect(vw, &ViewerWidget::ColorManagerChanged, this, &ViewerPanelBase::ColorManagerChanged); + connect(vw, &ViewerWidget::MulticamNodeDetected, this, &ViewerPanelBase::MulticamNodeDetected); SetTimeBasedWidget(vw); } diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index feaf4045e..41925a537 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -72,9 +72,14 @@ public: GetViewerWidget()->AddPlaybackDevice(vw); } - void SetMulticamNode(MultiCamNode *n) + void SetTimelineSelectedBlocks(const QVector &b) { - GetViewerWidget()->SetMulticamNode(n); + GetViewerWidget()->SetTimelineSelectedBlocks(b); + } + + void ConnectMulticamPanel(MulticamPanel *p) + { + GetViewerWidget()->ConnectMulticamPanel(p); } public slots: @@ -105,6 +110,8 @@ signals: */ void ColorManagerChanged(ColorManager* color_manager); + void MulticamNodeDetected(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip); + protected: void SetViewerWidget(ViewerWidget *vw); diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 3ac92c20e..9d77b6a7c 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -19,6 +19,7 @@ ***/ #include "multicamwidget.h" +#include "qshortcut.h" #include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/timeruler/timeruler.h" #include "widget/timelinewidget/undo/timelineundosplit.h" @@ -48,6 +49,11 @@ MulticamWidget::MulticamWidget(QWidget *parent) : layout->addWidget(this->ruler()); layout->addWidget(this->scrollbar()); + + for (int i=0; i<9; i++) { + new QShortcut(QStringLiteral("Ctrl+%1").arg(QString::number(i+1)), this, this, [this, i]{Switch(i, false);}); + new QShortcut(QString::number(i+1), this, this, [this, i]{Switch(i, true);}); + } } void MulticamWidget::SetMulticamNode(MultiCamNode *n) @@ -77,6 +83,45 @@ void MulticamWidget::DisconnectNodeEvent(ViewerOutput *n) disconnect(n, &ViewerOutput::PixelAspectChanged, sizer_, &ViewerSizer::SetPixelAspectRatio); } +void MulticamWidget::Switch(int source, bool split_clip) +{ + if (!node_) { + return; + } + + MultiUndoCommand *command = new MultiUndoCommand(); + + MultiCamNode *cam = node_; + ClipBlock *clip = clip_; + + if (clip_ && split_clip && clip_->in() < GetTime() && clip_->out() > GetTime()) { + QVector blocks; + + blocks.append(clip_); + blocks.append(clip_->block_links()); + + auto split = new BlockSplitPreservingLinksCommand(blocks, {GetTime()}); + split->redo_now(); + command->add_child(split); + + clip = static_cast(split->GetSplit(clip_, 0)); + + cam = clip->FindMulticam(); + } + + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(cam, cam->kCurrentInput)), source)); + Core::instance()->undo_stack()->push(command); + + if (cam != node_) { + SetMulticamNode(cam); + } + if (clip != clip_) { + SetClip(clip); + } + + display_->update(); +} + void MulticamWidget::DisplayClicked(const QPoint &p) { if (!node_) { @@ -99,37 +144,9 @@ void MulticamWidget::DisplayClicked(const QPoint &p) int c = click.x() / (width/multi); int r = click.y() / (height/multi); - MultiUndoCommand *command = new MultiUndoCommand(); + int source = node_->RowsColsToIndex(r, c, rows, cols); - const bool enable_split = true; - - MultiCamNode *cam = node_; - ClipBlock *clip = clip_; - - if (clip_ && enable_split && clip_->in() < GetTime() && clip_->out() > GetTime()) { - QVector blocks; - - blocks.append(clip_); - blocks.append(clip_->block_links()); - - auto split = new BlockSplitPreservingLinksCommand(blocks, {GetTime()}); - split->redo_now(); - command->add_child(split); - - clip = static_cast(split->GetSplit(clip_, 0)); - - cam = clip->FindMulticam(); - } - - command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(cam, cam->kCurrentInput)), cam->RowsColsToIndex(r, c, rows, cols))); - Core::instance()->undo_stack()->push(command); - - if (cam != node_) { - SetMulticamNode(cam); - } - if (clip != clip_) { - SetClip(clip); - } + Switch(source, true); } } diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h index 36380861b..c68b1945a 100644 --- a/app/widget/multicam/multicamwidget.h +++ b/app/widget/multicam/multicamwidget.h @@ -44,6 +44,8 @@ protected: virtual void DisconnectNodeEvent(ViewerOutput *n) override; private: + void Switch(int source, bool split_clip); + ViewerSizer *sizer_; MulticamDisplay *display_; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 7382a2b3f..ac741f9da 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -40,6 +40,8 @@ #include "node/block/gap/gap.h" #include "node/generator/shape/shapenodebase.h" #include "node/project/project.h" +#include "panel/multicam/multicampanel.h" +#include "panel/panelmanager.h" #include "render/rendermanager.h" #include "viewerpreventsleep.h" #include "widget/audiomonitor/audiomonitor.h" @@ -75,7 +77,8 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent) : first_requeue_watcher_(nullptr), enable_audio_scrubbing_(true), waveform_mode_(kWFAutomatic), - ignore_scrub_(0) + ignore_scrub_(0), + multicam_panel_(nullptr) { // Set up main layout QVBoxLayout* layout = new QVBoxLayout(this); @@ -604,6 +607,53 @@ void ViewerWidget::SetWaveformMode(WaveformMode wf) UpdateWaveformViewFromMode(); } +void ViewerWidget::DetectMulticamNode(const rational &time) +{ + // Look for multicam node + MultiCamNode *multicam = nullptr; + ClipBlock *clip = nullptr; + + // Faster way to do this + if (multicam_panel_ && multicam_panel_->isVisible()) { + if (Sequence *s = dynamic_cast(GetConnectedNode())) { + // Prefer selected blocks + for (Block *b : timeline_selected_blocks_) { + if (b->range().Contains(time)) { + if ((clip = dynamic_cast(b))) { + if ((multicam = clip->FindMulticam())) { + break; + } + } + } + } + + if (!multicam) { + const QVector &tracks = s->GetTracks(); + for (Track *t : tracks) { + if (t->IsLocked()) { + continue; + } + + Block *b = t->NearestBlockBeforeOrAt(time); + if ((clip = dynamic_cast(b))) { + if ((multicam = clip->FindMulticam())) { + break; + } + } + } + } + } + } + + if (multicam) { + emit MulticamNodeDetected(GetConnectedNode(), multicam, clip); + auto_cacher()->SetMulticamNode(multicam); + } else { + auto_cacher()->SetMulticamNode(nullptr); + emit MulticamNodeDetected(nullptr, nullptr, nullptr); + } +} + void ViewerWidget::UpdateWaveformViewFromMode() { bool prefer_waveform = ShouldForceWaveform(); @@ -790,6 +840,8 @@ void ViewerWidget::UpdateTextureFromNode() // Clear queue because we want this frame more than any others auto_cacher_->ClearSingleFrameRenders(); + DetectMulticamNode(time); + watcher->SetTicket(GetFrame(time)); } else { // There is definitely no frame here, we can immediately flip to showing nothing @@ -799,14 +851,6 @@ void ViewerWidget::UpdateTextureFromNode() } } -void ViewerWidget::SetMulticamNode(MultiCamNode *n) -{ - auto_cacher()->SetMulticamNode(n); - if (!IsPlaying()) { - UpdateTextureFromNode(); - } -} - void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) { Q_ASSERT(speed != 0); @@ -1035,6 +1079,7 @@ RenderTicketWatcher *ViewerWidget::RequestNextFrameForQueue(bool increment) watcher = new RenderTicketWatcher(); watcher->setProperty("time", QVariant::fromValue(next_time)); + DetectMulticamNode(next_time); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue); queue_watchers_.append(watcher); watcher->SetTicket(GetFrame(next_time)); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 5acff6cc3..35ce4147a 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -43,6 +43,8 @@ namespace olive { +class MulticamPanel; + /** * @brief An OpenGL-based viewer widget with playback controls (a PlaybackControls widget). */ @@ -109,7 +111,18 @@ public: playback_devices_.push_back(vw); } - void SetMulticamNode(MultiCamNode *n); + void SetTimelineSelectedBlocks(const QVector &b) + { + timeline_selected_blocks_ = b; + + if (!IsPlaying()) { + // If is playing, this will happen by the next frame automatically + DetectMulticamNode(GetTime()); + UpdateTextureFromNode(); + } + } + + void ConnectMulticamPanel(MulticamPanel *p) { multicam_panel_ = p; } public slots: void Play(bool in_to_out_only); @@ -167,6 +180,8 @@ signals: */ void ColorManagerChanged(ColorManager* color_manager); + void MulticamNodeDetected(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip); + protected: ViewerWidget(ViewerDisplayWidget *display, QWidget* parent = nullptr); @@ -256,6 +271,8 @@ private: void SetWaveformMode(WaveformMode wf); + void DetectMulticamNode(const rational &time); + ViewerSizer* sizer_; int playback_speed_; @@ -324,6 +341,10 @@ private: int ignore_scrub_; + QVector timeline_selected_blocks_; + + MulticamPanel *multicam_panel_; + private slots: void PlaybackTimerUpdate(); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 256f6abab..813bab9aa 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -38,8 +38,7 @@ namespace olive { MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - last_multicam_panel_(nullptr) + QMainWindow(parent) { // Resizes main window to desktop geometry on startup. Fixes the following issues: // * Qt on Windows has a bug that "de-maximizes" the window when widgets are added, resizing the @@ -119,6 +118,8 @@ MainWindow::MainWindow(QWidget *parent) : connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); sequence_viewer_panel_->AddPlaybackDevice(multicam_panel_->GetMulticamWidget()->GetDisplayWidget()); + sequence_viewer_panel_->ConnectMulticamPanel(multicam_panel_); + connect(sequence_viewer_panel_, &ViewerPanelBase::MulticamNodeDetected, multicam_panel_, &MulticamPanel::SetMulticamNode); scope_panel_->SetViewerPanel(sequence_viewer_panel_); @@ -492,58 +493,7 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) if (PanelManager::instance()->CurrentlyFocused(false) == panel) { UpdateNodePanelContextFromTimelinePanel(panel); - - last_multicam_panel_ = panel; - UpdateMulticamNode(); - } -} - -void MainWindow::UpdateMulticamNode() -{ - TimelinePanel *panel = last_multicam_panel_; - if (!panel) { - return; - } - - ClipBlock *clip = nullptr; - MultiCamNode *multicam = nullptr; - - for (Block *b : panel->GetSelectedBlocks()) { - if (b->range().Contains(panel->GetTime())) { - if ((clip = dynamic_cast(b))) { - if ((multicam = clip->FindMulticam())) { - break; - } - } - } - } - - if (!multicam && panel->GetSequence()) { - const QVector &tracks = panel->GetSequence()->GetTracks(); - for (Track *t : tracks) { - if (t->IsLocked()) { - continue; - } - - Block *b = t->NearestBlockBeforeOrAt(panel->GetTime()); - if ((clip = dynamic_cast(b))) { - if ((multicam = clip->FindMulticam())) { - break; - } - } - } - } - - if (multicam) { - multicam_panel_->SetMulticamNode(multicam); - sequence_viewer_panel_->SetMulticamNode(multicam); - multicam_panel_->SetClip(clip); - multicam_panel_->ConnectViewerNode(panel->GetConnectedViewer()); - } else { - multicam_panel_->ConnectViewerNode(nullptr); - sequence_viewer_panel_->SetMulticamNode(nullptr); - multicam_panel_->SetMulticamNode(nullptr); - multicam_panel_->SetClip(nullptr); + sequence_viewer_panel_->SetTimelineSelectedBlocks(blocks); } } @@ -659,8 +609,6 @@ void MainWindow::UpdateMainTimePanels(const rational &r) p->SetTime(r); } } - - UpdateMulticamNode(); } TimelinePanel* MainWindow::AppendTimelinePanel() @@ -693,9 +641,6 @@ ProjectPanel *MainWindow::AppendProjectPanel() void MainWindow::RemoveTimelinePanel(TimelinePanel *panel) { // Stop showing this timeline in the viewer - if (last_multicam_panel_ == panel) { - last_multicam_panel_ = nullptr; - } TimelineFocused(nullptr); panel->ConnectViewerNode(nullptr); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 7b0e8979c..7f319e4b1 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -147,8 +147,6 @@ private: void SelectFootageForProjectPanel(const QVector &e, ProjectPanel *p); - void UpdateMulticamNode(); - void AddMainTimePanel(TimeBasedPanel *p); QByteArray premaximized_state_; @@ -180,8 +178,6 @@ private: QVector main_time_panels_; - TimelinePanel *last_multicam_panel_; - private slots: void FocusedPanelChanged(PanelWidget* panel); From 14c7bd6dbce1a1a5390b022e48dcb5dc604a1998 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 2 Oct 2022 10:39:35 -0700 Subject: [PATCH 29/36] multicam: implemented queuing of future node selections during playback --- app/panel/multicam/multicampanel.h | 8 ------ app/panel/viewer/viewerbase.cpp | 9 ++++--- app/panel/viewer/viewerbase.h | 6 ++--- app/widget/multicam/multicamwidget.cpp | 34 ++++++++++++++++++-------- app/widget/multicam/multicamwidget.h | 17 ++++++++++--- app/widget/viewer/viewer.cpp | 13 ++++++++-- app/widget/viewer/viewer.h | 8 +++--- app/window/mainwindow/mainwindow.cpp | 3 +-- 8 files changed, 60 insertions(+), 38 deletions(-) diff --git a/app/panel/multicam/multicampanel.h b/app/panel/multicam/multicampanel.h index ffa9616d0..1b6dee6fc 100644 --- a/app/panel/multicam/multicampanel.h +++ b/app/panel/multicam/multicampanel.h @@ -14,14 +14,6 @@ public: MulticamWidget *GetMulticamWidget() const { return static_cast(GetTimeBasedWidget()); } -public slots: - void SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip) - { - ConnectViewerNode(viewer); - GetMulticamWidget()->SetMulticamNode(n); - GetMulticamWidget()->SetClip(clip); - } - protected: virtual void Retranslate() override; diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp index ac575699e..c1aac4d13 100644 --- a/app/panel/viewer/viewerbase.cpp +++ b/app/panel/viewer/viewerbase.cpp @@ -100,16 +100,17 @@ void ViewerPanelBase::SetViewerWidget(ViewerWidget *vw) connect(vw, &ViewerWidget::TextureChanged, this, &ViewerPanelBase::TextureChanged); connect(vw, &ViewerWidget::ColorProcessorChanged, this, &ViewerPanelBase::ColorProcessorChanged); connect(vw, &ViewerWidget::ColorManagerChanged, this, &ViewerPanelBase::ColorManagerChanged); - connect(vw, &ViewerWidget::MulticamNodeDetected, this, &ViewerPanelBase::MulticamNodeDetected); SetTimeBasedWidget(vw); } void ViewerPanelBase::FocusedPanelChanged(PanelWidget *panel) { - auto vw = GetViewerWidget(); - if (vw->IsPlaying() && panel != this) { - vw->Pause(); + if (dynamic_cast(panel)) { + auto vw = GetViewerWidget(); + if (vw->IsPlaying() && panel != this) { + vw->Pause(); + } } } diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index 41925a537..60122c262 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -77,9 +77,9 @@ public: GetViewerWidget()->SetTimelineSelectedBlocks(b); } - void ConnectMulticamPanel(MulticamPanel *p) + void ConnectMulticamWidget(MulticamWidget *p) { - GetViewerWidget()->ConnectMulticamPanel(p); + GetViewerWidget()->ConnectMulticamWidget(p); } public slots: @@ -110,8 +110,6 @@ signals: */ void ColorManagerChanged(ColorManager* color_manager); - void MulticamNodeDetected(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip); - protected: void SetViewerWidget(ViewerWidget *vw); diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 9d77b6a7c..2cc388107 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -56,15 +56,23 @@ MulticamWidget::MulticamWidget(QWidget *parent) : } } -void MulticamWidget::SetMulticamNode(MultiCamNode *n) +void MulticamWidget::SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip) { + ConnectViewerNode(viewer); node_ = n; display_->SetMulticamNode(n); + clip_ = clip; } -void MulticamWidget::SetClip(ClipBlock *clip) +void MulticamWidget::SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time) { - clip_ = clip; + if (time == rational::NaN || time == GetTime()) { + SetMulticamNodeInternal(viewer, n, clip); + play_queue_.clear(); + } else { + MulticamNodeQueue m = {time, viewer, n, clip}; + play_queue_.push_back(m); + } } void MulticamWidget::ConnectNodeEvent(ViewerOutput *n) @@ -83,6 +91,19 @@ void MulticamWidget::DisconnectNodeEvent(ViewerOutput *n) disconnect(n, &ViewerOutput::PixelAspectChanged, sizer_, &ViewerSizer::SetPixelAspectRatio); } +void MulticamWidget::TimeChangedEvent(const rational &t) +{ + super::TimeChangedEvent(t); + + if (!play_queue_.empty()) { + const MulticamNodeQueue &m = play_queue_.front(); + if (m.time >= t) { + SetMulticamNodeInternal(m.viewer, m.node, m.clip); + play_queue_.pop_front(); + } + } +} + void MulticamWidget::Switch(int source, bool split_clip) { if (!node_) { @@ -112,13 +133,6 @@ void MulticamWidget::Switch(int source, bool split_clip) command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(cam, cam->kCurrentInput)), source)); Core::instance()->undo_stack()->push(command); - if (cam != node_) { - SetMulticamNode(cam); - } - if (clip != clip_) { - SetClip(clip); - } - display_->update(); } diff --git a/app/widget/multicam/multicamwidget.h b/app/widget/multicam/multicamwidget.h index c68b1945a..8691c5668 100644 --- a/app/widget/multicam/multicamwidget.h +++ b/app/widget/multicam/multicamwidget.h @@ -35,15 +35,16 @@ public: MulticamDisplay *GetDisplayWidget() const { return display_; } - void SetMulticamNode(MultiCamNode *n); - - void SetClip(ClipBlock *clip); + void SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time); protected: virtual void ConnectNodeEvent(ViewerOutput *n) override; virtual void DisconnectNodeEvent(ViewerOutput *n) override; + virtual void TimeChangedEvent(const rational &t) override; private: + void SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip); + void Switch(int source, bool split_clip); ViewerSizer *sizer_; @@ -54,6 +55,16 @@ private: ClipBlock *clip_; + struct MulticamNodeQueue + { + rational time; + ViewerOutput *viewer; + MultiCamNode *node; + ClipBlock *clip; + }; + + std::list play_queue_; + private slots: void DisplayClicked(const QPoint &p); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index ac741f9da..fa8c2ac52 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -419,6 +419,11 @@ void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, c recording_track_ = track; } +void ViewerWidget::ConnectMulticamWidget(MulticamWidget *p) +{ + multicam_panel_ = p; +} + FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QUuid &cache_id, const int64_t& time) { FramePtr frame = FrameHashCache::LoadCacheFrame(cache_path, cache_id, time); @@ -646,11 +651,15 @@ void ViewerWidget::DetectMulticamNode(const rational &time) } if (multicam) { - emit MulticamNodeDetected(GetConnectedNode(), multicam, clip); + if (multicam_panel_) { + multicam_panel_->SetMulticamNode(GetConnectedNode(), multicam, clip, time); + } auto_cacher()->SetMulticamNode(multicam); } else { auto_cacher()->SetMulticamNode(nullptr); - emit MulticamNodeDetected(nullptr, nullptr, nullptr); + if (multicam_panel_) { + multicam_panel_->SetMulticamNode(nullptr, nullptr, nullptr, time); + } } } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 35ce4147a..a5312eb3b 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -43,7 +43,7 @@ namespace olive { -class MulticamPanel; +class MulticamWidget; /** * @brief An OpenGL-based viewer widget with playback controls (a PlaybackControls widget). @@ -122,7 +122,7 @@ public: } } - void ConnectMulticamPanel(MulticamPanel *p) { multicam_panel_ = p; } + void ConnectMulticamWidget(MulticamWidget *p); public slots: void Play(bool in_to_out_only); @@ -180,8 +180,6 @@ signals: */ void ColorManagerChanged(ColorManager* color_manager); - void MulticamNodeDetected(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip); - protected: ViewerWidget(ViewerDisplayWidget *display, QWidget* parent = nullptr); @@ -343,7 +341,7 @@ private: QVector timeline_selected_blocks_; - MulticamPanel *multicam_panel_; + MulticamWidget *multicam_panel_; private slots: void PlaybackTimerUpdate(); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 813bab9aa..dfb5e11a2 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -118,8 +118,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); sequence_viewer_panel_->AddPlaybackDevice(multicam_panel_->GetMulticamWidget()->GetDisplayWidget()); - sequence_viewer_panel_->ConnectMulticamPanel(multicam_panel_); - connect(sequence_viewer_panel_, &ViewerPanelBase::MulticamNodeDetected, multicam_panel_, &MulticamPanel::SetMulticamNode); + sequence_viewer_panel_->ConnectMulticamWidget(multicam_panel_->GetMulticamWidget()); scope_panel_->SetViewerPanel(sequence_viewer_panel_); From ecb901ea9acf33ce2ad0ea3619365c3e11007da4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 2 Oct 2022 10:48:24 -0700 Subject: [PATCH 30/36] multicamwidget: look for multicams in clip links --- app/widget/multicam/multicamwidget.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 2cc388107..16fa7e5c8 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -115,13 +115,15 @@ void MulticamWidget::Switch(int source, bool split_clip) MultiCamNode *cam = node_; ClipBlock *clip = clip_; + BlockSplitPreservingLinksCommand *split = nullptr; + if (clip_ && split_clip && clip_->in() < GetTime() && clip_->out() > GetTime()) { QVector blocks; blocks.append(clip_); blocks.append(clip_->block_links()); - auto split = new BlockSplitPreservingLinksCommand(blocks, {GetTime()}); + split = new BlockSplitPreservingLinksCommand(blocks, {GetTime()}); split->redo_now(); command->add_child(split); @@ -131,6 +133,15 @@ void MulticamWidget::Switch(int source, bool split_clip) } command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(cam, cam->kCurrentInput)), source)); + + for (Block *link : clip->block_links()) { + if (ClipBlock *clink = dynamic_cast(link)) { + if (MultiCamNode *mlink = clink->FindMulticam()) { + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(mlink, mlink->kCurrentInput)), source)); + } + } + } + Core::instance()->undo_stack()->push(command); display_->update(); From 2f9ec9d13e952c4972af36520578ce36e4e4ab06 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:00:14 -0700 Subject: [PATCH 31/36] node: improved input error reporting --- app/node/node.cpp | 75 +++++++++++++++++++++++------------------------ app/node/node.h | 2 +- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/app/node/node.cpp b/app/node/node.cpp index 1c2ec5c4e..c677b130f 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -27,11 +27,8 @@ #include "common/bezier.h" #include "common/lerp.h" -#include "common/timecodefunctions.h" -#include "common/xmlutils.h" #include "core.h" #include "config/config.h" -#include "node/project/footage/footage.h" #include "project/project.h" #include "ui/colorcoding.h" #include "ui/icons/icons.h" @@ -251,7 +248,7 @@ QString Node::GetInputName(const QString &id) const if (i) { return i->human_name; } else { - ReportInvalidInput("get name of", id); + ReportInvalidInput("get name of", id, -1); return QString(); } } @@ -278,7 +275,7 @@ bool Node::IsInputKeyframing(const QString &input, int element) const if (imm) { return imm->is_keyframing(); } else { - ReportInvalidInput("get keyframing state of", input); + ReportInvalidInput("get keyframing state of", input, element); return false; } } @@ -297,7 +294,7 @@ void Node::SetInputIsKeyframing(const QString &input, bool e, int element) emit KeyframeEnableChanged(NodeInput(this, input, element), e); } else { - ReportInvalidInput("set keyframing state of", input); + ReportInvalidInput("set keyframing state of", input, element); } } @@ -324,7 +321,7 @@ bool Node::IsUsingStandardValue(const QString &input, int track, int element) co if (imm) { return imm->is_using_standard_value(track); } else { - ReportInvalidInput("determine whether using standard value in", input); + ReportInvalidInput("determine whether using standard value in", input, element); return true; } } @@ -336,7 +333,7 @@ NodeValue::Type Node::GetInputDataType(const QString &id) const if (i) { return i->type; } else { - ReportInvalidInput("get data type of", id); + ReportInvalidInput("get data type of", id, -1); return NodeValue::kNone; } } @@ -355,7 +352,7 @@ void Node::SetInputDataType(const QString &id, const NodeValue::Type &type) emit InputDataTypeChanged(id, type); } else { - ReportInvalidInput("set data type of", id); + ReportInvalidInput("set data type of", id, -1); } } @@ -366,7 +363,7 @@ bool Node::HasInputProperty(const QString &id, const QString &name) const if (i) { return i->properties.contains(name); } else { - ReportInvalidInput("get property of", id); + ReportInvalidInput("get property of", id, -1); return false; } } @@ -378,7 +375,7 @@ QHash Node::GetInputProperties(const QString &id) const if (i) { return i->properties; } else { - ReportInvalidInput("get property table of", id); + ReportInvalidInput("get property table of", id, -1); return QHash(); } } @@ -390,7 +387,7 @@ QVariant Node::GetInputProperty(const QString &id, const QString &name) const if (i) { return i->properties.value(name); } else { - ReportInvalidInput("get property of", id); + ReportInvalidInput("get property of", id, -1); return QVariant(); } } @@ -404,7 +401,7 @@ void Node::SetInputProperty(const QString &id, const QString &name, const QVaria emit InputPropertyChanged(id, name, value); } else { - ReportInvalidInput("set property of", id); + ReportInvalidInput("set property of", id, -1); } } @@ -548,7 +545,7 @@ SplitValue Node::GetSplitDefaultValue(const QString &input) const if (i) { return i->default_value; } else { - ReportInvalidInput("retrieve default value of", input); + ReportInvalidInput("retrieve default value of", input, -1); return SplitValue(); } } @@ -577,7 +574,7 @@ void Node::SetSplitDefaultValue(const QString &input, const SplitValue &val) if (i) { i->default_value = val; } else { - ReportInvalidInput("set default value of", input); + ReportInvalidInput("set default value of", input, -1); } } @@ -590,7 +587,7 @@ void Node::SetSplitDefaultValueOnTrack(const QString &input, const QVariant &val i->default_value[track] = val; } } else { - ReportInvalidInput("set default value on track of", input); + ReportInvalidInput("set default value on track of", input, -1); } } @@ -606,7 +603,7 @@ QVector Node::GetKeyframesAtTime(const QString &input, const rat if (imm) { return imm->get_keyframe_at_time(time); } else { - ReportInvalidInput("get keyframes at time from", input); + ReportInvalidInput("get keyframes at time from", input, element); return QVector(); } } @@ -618,7 +615,7 @@ NodeKeyframe *Node::GetKeyframeAtTimeOnTrack(const QString &input, const rationa if (imm) { return imm->get_keyframe_at_time_on_track(time, track); } else { - ReportInvalidInput("get keyframe at time on track from", input); + ReportInvalidInput("get keyframe at time on track from", input, element); return nullptr; } } @@ -630,7 +627,7 @@ NodeKeyframe::Type Node::GetBestKeyframeTypeForTimeOnTrack(const QString &input, if (imm) { return imm->get_best_keyframe_type_for_time(time, track); } else { - ReportInvalidInput("get closest keyframe before a time from", input); + ReportInvalidInput("get closest keyframe before a time from", input, element); return NodeKeyframe::kDefaultType; } } @@ -647,7 +644,7 @@ NodeKeyframe *Node::GetEarliestKeyframe(const QString &id, int element) const if (imm) { return imm->get_earliest_keyframe(); } else { - ReportInvalidInput("get earliest keyframe from", id); + ReportInvalidInput("get earliest keyframe from", id, element); return nullptr; } } @@ -659,7 +656,7 @@ NodeKeyframe *Node::GetLatestKeyframe(const QString &id, int element) const if (imm) { return imm->get_latest_keyframe(); } else { - ReportInvalidInput("get latest keyframe from", id); + ReportInvalidInput("get latest keyframe from", id, element); return nullptr; } } @@ -671,7 +668,7 @@ NodeKeyframe *Node::GetClosestKeyframeBeforeTime(const QString &id, const ration if (imm) { return imm->get_closest_keyframe_before_time(time); } else { - ReportInvalidInput("get closest keyframe before a time from", id); + ReportInvalidInput("get closest keyframe before a time from", id, element); return nullptr; } } @@ -683,7 +680,7 @@ NodeKeyframe *Node::GetClosestKeyframeAfterTime(const QString &id, const rationa if (imm) { return imm->get_closest_keyframe_after_time(time); } else { - ReportInvalidInput("get closest keyframe after a time from", id); + ReportInvalidInput("get closest keyframe after a time from", id, element); return nullptr; } } @@ -695,7 +692,7 @@ bool Node::HasKeyframeAtTime(const QString &id, const rational &time, int elemen if (imm) { return imm->has_keyframe_at_time(time); } else { - ReportInvalidInput("determine if it has a keyframe at a time from", id); + ReportInvalidInput("determine if it has a keyframe at a time from", id, element); return false; } } @@ -719,7 +716,7 @@ SplitValue Node::GetSplitStandardValue(const QString &id, int element) const if (imm) { return imm->get_split_standard_value(); } else { - ReportInvalidInput("get standard value of", id); + ReportInvalidInput("get standard value of", id, element); return SplitValue(); } } @@ -731,7 +728,7 @@ QVariant Node::GetSplitStandardValueOnTrack(const QString &input, int track, int if (imm) { return imm->get_split_standard_value_on_track(track); } else { - ReportInvalidInput("get standard value of", input); + ReportInvalidInput("get standard value of", input, element); return QVariant(); } } @@ -758,7 +755,7 @@ void Node::SetSplitStandardValue(const QString &id, const SplitValue &value, int } } } else { - ReportInvalidInput("set standard value of", id); + ReportInvalidInput("set standard value of", id, element); } } @@ -774,7 +771,7 @@ void Node::SetSplitStandardValueOnTrack(const QString &id, int track, const QVar ParameterValueChanged(id, element, TimeRange(RATIONAL_MIN, RATIONAL_MAX)); } } else { - ReportInvalidInput("set standard value of", id); + ReportInvalidInput("set standard value of", id, element); } } @@ -873,7 +870,7 @@ int Node::InputArraySize(const QString &id) const if (i) { return i->array_size; } else { - ReportInvalidInput("retrieve array size of", id); + ReportInvalidInput("retrieve array size of", id, -1); return 0; } } @@ -914,7 +911,7 @@ InputFlags Node::GetInputFlags(const QString &input) const if (i) { return i->flags; } else { - ReportInvalidInput("retrieve flags of", input); + ReportInvalidInput("retrieve flags of", input, -1); return InputFlags(kInputFlagNormal); } } @@ -927,7 +924,7 @@ void Node::SetInputFlags(const QString &input, const InputFlags &f) i->flags = f; emit InputFlagsChanged(input, i->flags); } else { - ReportInvalidInput("set flags of", input); + ReportInvalidInput("set flags of", input, -1); } } @@ -1227,7 +1224,7 @@ void Node::RemoveInput(const QString &id) int index = input_ids_.indexOf(id); if (index == -1) { - ReportInvalidInput("remove", id); + ReportInvalidInput("remove", id, -1); return; } @@ -1237,9 +1234,9 @@ void Node::RemoveInput(const QString &id) emit InputRemoved(id); } -void Node::ReportInvalidInput(const char *attempted_action, const QString& id) const +void Node::ReportInvalidInput(const char *attempted_action, const QString& id, int element) const { - qWarning() << "Failed to" << attempted_action << "parameter" << id + qWarning() << "Failed to" << attempted_action << "parameter" << id << "element" << element << "in node" << this->id() << "- input doesn't exist"; } @@ -1250,7 +1247,7 @@ NodeInputImmediate *Node::CreateImmediate(const QString &input) if (i) { return new NodeInputImmediate(i->type, i->default_value); } else { - ReportInvalidInput("create immediate", input); + ReportInvalidInput("create immediate", input, -1); return nullptr; } } @@ -1260,7 +1257,7 @@ void Node::ArrayResizeInternal(const QString &id, int size) Input* imm = GetInternalInputData(id); if (!imm) { - ReportInvalidInput("set array size", id); + ReportInvalidInput("set array size", id, -1); return; } @@ -1299,7 +1296,7 @@ void Node::SetInputName(const QString &id, const QString &name) emit InputNameChanged(id, name); } else { - ReportInvalidInput("set name of", id); + ReportInvalidInput("set name of", id, -1); } } @@ -1426,8 +1423,8 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, } } - foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) { - foreach (NodeKeyframe* key, track) { + for (const NodeKeyframeTrack& track : src->GetImmediate(input, src_element)->keyframe_tracks()) { + for (NodeKeyframe* key : track) { NodeKeyframe *copy = key->copy(dst_element, command ? nullptr : dst); if (command) { command->add_child(new NodeParamInsertKeyframeCommand(dst, copy)); diff --git a/app/node/node.h b/app/node/node.h index 17aefb72a..3ac69a6f9 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -1352,7 +1352,7 @@ private: } } - void ReportInvalidInput(const char* attempted_action, const QString &id) const; + void ReportInvalidInput(const char* attempted_action, const QString &id, int element) const; void ArrayResizeInternal(const QString& id, int size); From 2aaf96d55bb8c85cfe887a08d31de50fd16bad10 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:00:32 -0700 Subject: [PATCH 32/36] multicamnode: only push active element if element exists --- app/node/input/multicam/multicamnode.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/node/input/multicam/multicamnode.cpp b/app/node/input/multicam/multicamnode.cpp index 5984e4b98..8bdb5a5dd 100644 --- a/app/node/input/multicam/multicamnode.cpp +++ b/app/node/input/multicam/multicamnode.cpp @@ -42,9 +42,14 @@ QString MultiCamNode::Description() const Node::ActiveElements MultiCamNode::GetActiveElementsAtTime(const QString &input, const TimeRange &r) const { if (input == kSourcesInput) { - Node::ActiveElements a; - a.add(GetCurrentSource()); - return a; + int src = GetCurrentSource(); + if (src >= 0 && src < InputArraySize(kSourcesInput)) { + Node::ActiveElements a; + a.add(src); + return a; + } else { + return ActiveElements::kNoElements; + } } else { return super::GetActiveElementsAtTime(input, r); } From 8a2f2912b40de1631af8365d49d714768fb6210b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:23:55 -0700 Subject: [PATCH 33/36] multicam: allow overrides by selecting multicam nodes in nodeview --- app/panel/viewer/viewerbase.h | 5 +++++ app/widget/viewer/viewer.cpp | 33 +++++++++++++++++++++++----- app/widget/viewer/viewer.h | 12 ++++++++++ app/window/mainwindow/mainwindow.cpp | 2 ++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index 60122c262..16c733b35 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -77,6 +77,11 @@ public: GetViewerWidget()->SetTimelineSelectedBlocks(b); } + void SetNodeViewSelections(const QVector &n) + { + GetViewerWidget()->SetNodeViewSelections(n); + } + void ConnectMulticamWidget(MulticamWidget *p) { GetViewerWidget()->ConnectMulticamWidget(p); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index fa8c2ac52..ef9d47390 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -260,6 +260,9 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateWaveformViewFromMode); + timeline_selected_blocks_.clear(); + node_view_selected_.clear(); + CloseAudioProcessor(); audio_scrub_watchers_.clear(); @@ -621,12 +624,30 @@ void ViewerWidget::DetectMulticamNode(const rational &time) // Faster way to do this if (multicam_panel_ && multicam_panel_->isVisible()) { if (Sequence *s = dynamic_cast(GetConnectedNode())) { - // Prefer selected blocks - for (Block *b : timeline_selected_blocks_) { - if (b->range().Contains(time)) { - if ((clip = dynamic_cast(b))) { - if ((multicam = clip->FindMulticam())) { - break; + // Prefer selected nodes + for (Node *n : qAsConst(node_view_selected_)) { + if ((multicam = dynamic_cast(n))) { + // Found multicam, now try to find corresponding clip from selected timeline blocks + for (Block *b : qAsConst(timeline_selected_blocks_)) { + if (ClipBlock *c = dynamic_cast(b)) { + if (c->range().Contains(time) && c->ContextContainsNode(multicam)) { + clip = c; + break; + } + } + } + break; + } + } + + // Next, prefer multicam from selected block + if (!multicam) { + for (Block *b : qAsConst(timeline_selected_blocks_)) { + if (b->range().Contains(time)) { + if ((clip = dynamic_cast(b))) { + if ((multicam = clip->FindMulticam())) { + break; + } } } } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index a5312eb3b..c465aabc9 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -122,6 +122,17 @@ public: } } + void SetNodeViewSelections(const QVector &n) + { + node_view_selected_ = n; + + if (!IsPlaying()) { + // If is playing, this will happen by the next frame automatically + DetectMulticamNode(GetTime()); + UpdateTextureFromNode(); + } + } + void ConnectMulticamWidget(MulticamWidget *p); public slots: @@ -340,6 +351,7 @@ private: int ignore_scrub_; QVector timeline_selected_blocks_; + QVector node_view_selected_; MulticamWidget *multicam_panel_; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index dfb5e11a2..79b589901 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -105,6 +105,8 @@ MainWindow::MainWindow(QWidget *parent) : connect(param_panel_, &ParamPanel::FocusedNodeChanged, curve_panel_, &CurvePanel::SetNode); connect(param_panel_, &ParamPanel::SelectedNodesChanged, node_panel_, &NodePanel::Select); + connect(node_panel_, &NodePanel::NodeSelectionChanged, sequence_viewer_panel_, &ViewerPanel::SetNodeViewSelections); + // Connect time signals together AddMainTimePanel(multicam_panel_); AddMainTimePanel(curve_panel_); From 681e66f34c25343617c8117b7a9b4ab613e68aaa Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 2 Oct 2022 12:02:50 -0700 Subject: [PATCH 34/36] multicam: ensure node is disconnected when closing --- app/widget/multicam/multicamwidget.cpp | 2 +- app/widget/viewer/viewer.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/widget/multicam/multicamwidget.cpp b/app/widget/multicam/multicamwidget.cpp index 16fa7e5c8..6afbd9ac8 100644 --- a/app/widget/multicam/multicamwidget.cpp +++ b/app/widget/multicam/multicamwidget.cpp @@ -66,7 +66,7 @@ void MulticamWidget::SetMulticamNodeInternal(ViewerOutput *viewer, MultiCamNode void MulticamWidget::SetMulticamNode(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip, const rational &time) { - if (time == rational::NaN || time == GetTime()) { + if (time.isNaN() || time == GetTime()) { SetMulticamNodeInternal(viewer, n, clip); play_queue_.clear(); } else { diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index ef9d47390..1ec330c19 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -262,6 +262,9 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) timeline_selected_blocks_.clear(); node_view_selected_.clear(); + if (multicam_panel_) { + multicam_panel_->SetMulticamNode(nullptr, nullptr, nullptr, rational::NaN); + } CloseAudioProcessor(); audio_scrub_watchers_.clear(); From a6315a780d878cdd7d0c8e96be65042497f9b61b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 2 Oct 2022 12:25:01 -0700 Subject: [PATCH 35/36] timeline: started implementation of multicam function --- app/widget/timelinewidget/timelinewidget.cpp | 39 ++++++++++++++++++++ app/widget/timelinewidget/timelinewidget.h | 3 ++ 2 files changed, 42 insertions(+) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index e53d77f0d..9941d061a 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1234,6 +1234,31 @@ void TimelineWidget::ShowContextMenu() QAction *reveal_in_project = menu.addAction(tr("Reveal in Project")); reveal_in_project->setData(reinterpret_cast(clip->connected_viewer())); connect(reveal_in_project, &QAction::triggered, this, &TimelineWidget::RevealInProject); + + /*if (Sequence *sequence = dynamic_cast(clip->connected_viewer())) { + Menu *multicam_menu = new Menu(tr("Multi-Cam"), &menu); + menu.addMenu(multicam_menu); + + QAction *multicam_enabled = multicam_menu->addAction(tr("Enabled")); + multicam_enabled->setCheckable(true); + + auto mcn = sequence->FindOutputNode(); + multicam_enabled->setChecked(!mcn.empty()); + + multicam_menu->addSeparator(); + + QAction *multicam_update = multicam_menu->addAction(tr("Update")); + multicam_update->setEnabled(!mcn.empty()); + + if (!mcn.empty()) { + auto n = mcn.first(); + multicam_enabled->setProperty("multicam", Node::PtrToValue(n)); + multicam_update->setProperty("multicam", Node::PtrToValue(n)); + } + + connect(multicam_enabled, &QAction::triggered, this, &TimelineWidget::MulticamEnabledTriggered); + connect(multicam_update, &QAction::triggered, this, &TimelineWidget::MulticamUpdateTriggered); + }*/ } } @@ -1465,6 +1490,20 @@ void TimelineWidget::CacheDiscard() } } +void TimelineWidget::MulticamEnabledTriggered(bool e) +{ + if (e) { + // Add multicam node + } else if (MultiCamNode *m = Node::ValueToPtr(sender()->property("multicam"))) { + // Remove multicam node + } +} + +void TimelineWidget::MulticamUpdateTriggered() +{ + // Update multicam node +} + void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost) { ghost_items_.append(ghost); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index dd081af0f..998e56869 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -446,6 +446,9 @@ private slots: void CacheClipsInOut(); void CacheDiscard(); + void MulticamEnabledTriggered(bool e); + void MulticamUpdateTriggered(); + }; } From 9d2a9a632c4b80c54dba4a5800c68bba6e27879b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 2 Oct 2022 14:35:36 -0700 Subject: [PATCH 36/36] textv3: fix possible var size issue --- app/node/generator/text/textv3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index afe900778..1964f8cb0 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -105,7 +105,7 @@ void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &global if (!args.empty()) { QStringList list; list.reserve(args.size()); - for (int i=0; i