From 5a24ac420b9cbe1bf77b8d10a931c2c7c6591cb3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 20 May 2022 10:19:09 -0700 Subject: [PATCH 01/53] remove autocache config entry Auto-cache can still be set from sequence settings --- app/config/config.cpp | 1 - app/dialog/sequence/sequence.cpp | 1 - app/dialog/sequence/sequencedialogpresettab.cpp | 4 ++-- app/node/output/viewer/viewer.cpp | 2 -- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index d2cb089db..e658322c0 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -140,7 +140,6 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("DefaultSequencePixelAspect"), NodeValue::kRational, QVariant::fromValue(rational(1))); SetEntryInternal(QStringLiteral("DefaultSequenceFrameRate"), NodeValue::kRational, QVariant::fromValue(rational(1001, 30000))); SetEntryInternal(QStringLiteral("DefaultSequenceInterlacing"), NodeValue::kInt, VideoParams::kInterlaceNone); - SetEntryInternal(QStringLiteral("DefaultSequenceAutoCache"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("DefaultSequenceAudioFrequency"), NodeValue::kInt, 48000); SetEntryInternal(QStringLiteral("DefaultSequenceAudioLayout"), NodeValue::kInt, QVariant::fromValue(static_cast(AV_CH_LAYOUT_STEREO))); diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index 5e8d08e8e..ce15018e8 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -155,7 +155,6 @@ void SequenceDialog::SetAsDefaultClicked() OLIVE_CONFIG("DefaultSequenceInterlacing") = parameter_tab_->GetSelectedVideoInterlacingMode(); OLIVE_CONFIG("DefaultSequenceAudioFrequency") = parameter_tab_->GetSelectedAudioSampleRate(); OLIVE_CONFIG("DefaultSequenceAudioLayout") = QVariant::fromValue(parameter_tab_->GetSelectedAudioChannelLayout()); - OLIVE_CONFIG("DefaultSequenceAutoCache") = QVariant::fromValue(parameter_tab_->GetSelectedPreviewAutoCache()); } } diff --git a/app/dialog/sequence/sequencedialogpresettab.cpp b/app/dialog/sequence/sequencedialogpresettab.cpp index b8a94fbe7..447824112 100644 --- a/app/dialog/sequence/sequencedialogpresettab.cpp +++ b/app/dialog/sequence/sequencedialogpresettab.cpp @@ -101,7 +101,7 @@ QTreeWidgetItem* SequenceDialogPresetTab::CreateFolder(const QString &name) QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width, int height, int divider) { const VideoParams::Format default_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()); - const bool default_autocache = OLIVE_CONFIG("DefaultSequenceAutoCache").toBool(); + const bool default_autocache = false; QTreeWidgetItem* parent = CreateFolder(name); AddStandardItem(parent, std::make_shared(tr("%1 23.976 FPS").arg(name), width, @@ -164,7 +164,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &name, int width, int height, const rational& frame_rate, const rational &standard_par, const rational &wide_par, int divider) { const VideoParams::Format default_format = static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()); - const bool default_autocache = OLIVE_CONFIG("DefaultSequenceAutoCache").toBool(); + const bool default_autocache = false; QTreeWidgetItem* parent = CreateFolder(name); preset_tree_->addTopLevelItem(parent); AddStandardItem(parent, std::make_shared(tr("%1 Standard").arg(name), diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 34629c4f7..aca810ab1 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -213,8 +213,6 @@ void ViewerOutput::set_default_parameters() OLIVE_CONFIG("DefaultSequenceAudioLayout").toULongLong(), AudioParams::kInternalFormat )); - - video_frame_cache()->SetEnabled(OLIVE_CONFIG("DefaultSequenceAutoCache").toBool()); } void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) From ee87ac27af52a7612efc119a15c7cfc08d3e4ba9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 21 May 2022 19:29:27 -0700 Subject: [PATCH 02/53] viewer: remove unnecessary lines from master --- app/node/output/viewer/viewer.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 20fb862aa..541698d55 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -222,15 +222,6 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, VerifyLength(); super::InvalidateCache(range, from, element, options); - - // TEMP: Just to restore the intended functionality for now. This will be removed later. - if (from == kTextureInput) { - TimeRange r = range.Intersected(TimeRange(0, GetVideoLength())); - if (r.length() != 0) video_frame_cache()->Invalidate(r); - } else if (from == kSamplesInput) { - TimeRange r = range.Intersected(TimeRange(0, GetAudioLength())); - if (r.length() != 0) audio_playback_cache()->Invalidate(r); - } } QVector ViewerOutput::GetEnabledStreamsAsReferences() const From 3cf01c1464522b14f767a0a7c9feeb95a49e9df5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 21 May 2022 19:55:04 -0700 Subject: [PATCH 03/53] timeline: show media in adj in ghost overlays --- app/widget/timelinewidget/tool/pointer.cpp | 1 + app/widget/timelinewidget/view/timelineview.cpp | 10 +++++----- app/widget/timelinewidget/view/timelineview.h | 5 +++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 7158cb906..693ac109c 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -568,6 +568,7 @@ void PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos) break; case Timeline::kTrimIn: ghost->SetInAdjustment(time_movement); + ghost->SetMediaInAdjustment(time_movement); break; case Timeline::kTrimOut: ghost->SetOutAdjustment(time_movement); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index d3ee50c04..22c5daa4e 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -315,9 +315,9 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) qreal old_opacity = painter->opacity(); painter->setOpacity(0.5); - rational in = ghost->GetAdjustedIn(), out = ghost->GetAdjustedOut(); - DrawBlock(painter, false, attached, track_top, track_height, in, out); - DrawBlock(painter, true, attached, track_top, track_height, in, out); + rational in = ghost->GetAdjustedIn(), out = ghost->GetAdjustedOut(), media_in = ghost->GetAdjustedMediaIn(); + DrawBlock(painter, false, attached, track_top, track_height, in, out, media_in); + DrawBlock(painter, true, attached, track_top, track_height, in, out, media_in); painter->setOpacity(old_opacity); } @@ -465,7 +465,7 @@ void TimelineView::DrawBlocks(QPainter *painter, bool foreground) } } -void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, qreal block_top, qreal block_height, const rational &in, const rational &out) +void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, qreal block_top, qreal block_height, const rational &in, const rational &out, const rational &media_in) { if (dynamic_cast(block) || dynamic_cast(block)) { @@ -524,7 +524,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q QRect waveform_rect = r.adjusted(0, text_total_height, 0, 0).toRect(); painter->setPen(shadow_color); AudioVisualWaveform::DrawWaveform(painter, waveform_rect, this->GetScale(), clip->waveform(), - SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base())); + SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in); } // Draw zebra stripes and markers diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 9f9425177..3dc5989b3 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -126,10 +126,11 @@ private: void DrawBlocks(QPainter* painter, bool foreground); - void DrawBlock(QPainter *painter, bool foreground, Block *block, qreal top, qreal height, const rational &in, const rational &out); + void DrawBlock(QPainter *painter, bool foreground, Block *block, qreal top, qreal height, const rational &in, const rational &out, const rational &media_in); void DrawBlock(QPainter *painter, bool foreground, Block *block, qreal top, qreal height) { - DrawBlock(painter, foreground, block, top, height, block->in(), block->out()); + ClipBlock *cb = dynamic_cast(block); + DrawBlock(painter, foreground, block, top, height, block->in(), block->out(), cb ? cb->media_in() : 0); } void DrawZebraStripes(QPainter *painter, const QRectF &r); From f124ad3178d93912e33f13443370c376e2bc8030 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 22 May 2022 20:54:59 -0700 Subject: [PATCH 04/53] cache waveforms at clip level --- app/dialog/sequence/sequence.cpp | 8 +- .../sequence/sequencedialogparametertab.cpp | 2 +- app/node/block/block.h | 3 + app/node/block/clip/clip.cpp | 37 +- app/node/block/clip/clip.h | 41 +- app/node/node.cpp | 10 +- app/node/node.h | 3 + app/node/output/viewer/viewer.h | 10 + app/render/audioplaybackcache.cpp | 9 + app/render/audioplaybackcache.h | 10 +- app/render/playbackcache.cpp | 19 +- app/render/playbackcache.h | 18 +- app/render/previewautocacher.cpp | 436 +++++++++--------- app/render/previewautocacher.h | 60 +-- app/render/renderprocessor.cpp | 20 - app/widget/timelinewidget/tool/pointer.cpp | 8 +- .../timelinewidget/undo/timelineundosplit.cpp | 2 +- .../timelinewidget/view/timelineview.cpp | 31 +- app/widget/timelinewidget/view/timelineview.h | 1 + app/widget/viewer/viewer.cpp | 28 +- app/widget/viewer/viewer.h | 2 +- 21 files changed, 429 insertions(+), 329 deletions(-) diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index ce15018e8..c999fd297 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -136,7 +136,7 @@ void SequenceDialog::accept() sequence_->SetVideoParams(video_params); sequence_->SetAudioParams(audio_params); sequence_->SetLabel(name_field_->text()); - sequence_->video_frame_cache()->SetEnabled(parameter_tab_->GetSelectedPreviewAutoCache()); + sequence_->video_frame_cache()->SetIsAutomatic(parameter_tab_->GetSelectedPreviewAutoCache()); } QDialog::accept(); @@ -170,7 +170,7 @@ SequenceDialog::SequenceParamCommand::SequenceParamCommand(Sequence* s, old_video_params_(s->GetVideoParams()), old_audio_params_(s->GetAudioParams()), old_name_(s->GetLabel()), - old_autocache_(s->video_frame_cache()->IsEnabled()) + old_autocache_(s->video_frame_cache()->IsAutomatic()) { } @@ -188,7 +188,7 @@ void SequenceDialog::SequenceParamCommand::redo() sequence_->SetAudioParams(new_audio_params_); } sequence_->SetLabel(new_name_); - sequence_->video_frame_cache()->SetEnabled(new_autocache_); + sequence_->video_frame_cache()->SetIsAutomatic(new_autocache_); } void SequenceDialog::SequenceParamCommand::undo() @@ -200,7 +200,7 @@ void SequenceDialog::SequenceParamCommand::undo() sequence_->SetAudioParams(old_audio_params_); } sequence_->SetLabel(old_name_); - sequence_->video_frame_cache()->SetEnabled(old_autocache_); + sequence_->video_frame_cache()->SetIsAutomatic(old_autocache_); } } diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp index 73eff0f1e..9e39aa005 100644 --- a/app/dialog/sequence/sequencedialogparametertab.cpp +++ b/app/dialog/sequence/sequencedialogparametertab.cpp @@ -89,7 +89,7 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg interlacing_combo_->SetInterlaceMode(vp.interlacing()); preview_resolution_field_->SetDivider(vp.divider()); preview_format_field_->SetPixelFormat(vp.format()); - preview_autocache_field_->setChecked(sequence->video_frame_cache()->IsEnabled()); + preview_autocache_field_->setChecked(sequence->video_frame_cache()->IsAutomatic()); audio_sample_rate_field_->SetSampleRate(ap.sample_rate()); audio_channels_field_->SetChannelLayout(ap.channel_layout()); diff --git a/app/node/block/block.h b/app/node/block/block.h index 3a65159e2..2ad077ee5 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -96,6 +96,7 @@ public: void set_track(Track* track) { track_ = track; + emit TrackChanged(track_); } bool is_enabled() const; @@ -126,6 +127,8 @@ signals: void PreviewChanged(); + void TrackChanged(Track *track); + protected: virtual void InputValueChangedEvent(const QString& input, int element) override; diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 241484965..b603d4af7 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -105,13 +105,7 @@ void ClipBlock::set_length_and_media_in(const rational &length) if (!reverse()) { // Calculate media_in adjustment rational proposed_media_in = SequenceToMediaTime(this->length() - length, false, true); - - waveform_.TrimIn(proposed_media_in - media_in()); - set_media_in(proposed_media_in); - } else { - // Trim waveform out point - waveform_.TrimIn(this->length() - length); } super::set_length_and_media_in(length); @@ -187,6 +181,19 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int // If signal is from texture input, transform all times from media time to sequence time if (from == kBufferIn) { + Track::Type type = GetTrackType(); + + if (type == Track::kVideo || type == Track::kAudio) { + if (Node *connected = GetConnectedOutput(from, element)) { + TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, length())); + if (type == Track::kVideo) { + emit connected->video_frame_cache()->Request(range.Intersected(max_range), true); + } else if (type == Track::kAudio) { + emit connected->audio_playback_cache()->Request(range.Intersected(max_range), true); + } + } + } + // Adjust range from media time to sequence time TimeRange adj; double speed_value = speed(); @@ -238,6 +245,24 @@ void ClipBlock::LinkChangeEvent() } } +void ClipBlock::InputConnectedEvent(const QString &input, int element, Node *output) +{ + super::InputConnectedEvent(input, element, output); + + if (input == kBufferIn) { + connect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); + } +} + +void ClipBlock::InputDisconnectedEvent(const QString &input, int element, Node *output) +{ + super::InputDisconnectedEvent(input, element, output); + + if (input == kBufferIn) { + disconnect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); + } +} + TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const { Q_UNUSED(element) diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index fce20dbbd..dd075b728 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -23,6 +23,7 @@ #include "audio/audiovisualwaveform.h" #include "node/block/block.h" +#include "node/output/track/track.h" namespace olive { @@ -46,6 +47,15 @@ public: virtual void set_length_and_media_out(const rational &length) override; virtual void set_length_and_media_in(const rational &length) override; + Track::Type GetTrackType() const + { + if (track()) { + return track()->type(); + } else { + return Track::kNone; + } + } + rational media_in() const; void set_media_in(const rational& media_in); @@ -109,9 +119,19 @@ public: return block_links_; } - AudioVisualWaveform& waveform() + const AudioVisualWaveform *waveform() { - return waveform_; + if (Node *n = GetConnectedOutput(kBufferIn)) { + return &n->audio_playback_cache()->visual(); + } else { + return nullptr; + } + } + + void set_waveform(const AudioVisualWaveform *w) + { + qDebug() << "WAVEFORM COPY STUB"; + //audio_playback_cache()->set_visual(w); } ViewerOutput *connected_viewer() const @@ -119,6 +139,16 @@ public: return connected_viewer_; } + virtual TimeRange GetVideoCacheRange() const override + { + return TimeRange(0, length()); + } + + virtual TimeRange GetAudioCacheRange() const override + { + return TimeRange(0, length()); + } + static const QString kBufferIn; static const QString kMediaInInput; static const QString kSpeedInput; @@ -128,6 +158,10 @@ public: protected: virtual void LinkChangeEvent() override; + virtual void InputConnectedEvent(const QString& input, int element, Node *output) override; + + virtual void InputDisconnectedEvent(const QString& input, int element, Node *output) override; + private: rational SequenceToMediaTime(const rational& sequence_time, bool ignore_reverse = false, bool ignore_speed = false) const; @@ -141,11 +175,8 @@ private: ViewerOutput *connected_viewer_; private: - AudioVisualWaveform waveform_; - rational last_media_in_; - }; } diff --git a/app/node/node.cpp b/app/node/node.cpp index 143f1e9ee..b931cf504 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -933,11 +933,13 @@ void Node::InvalidateCache(const TimeRange &range, const QString &from, int elem Q_UNUSED(element) if (range.in() != range.out()) { - if (video_cache_->IsEnabled()) { - video_frame_cache()->Invalidate(range); + TimeRange vr = range.Intersected(GetVideoCacheRange()); + if (vr.length() != 0) { + video_frame_cache()->Invalidate(vr); } - if (audio_cache_->IsEnabled()) { - audio_playback_cache()->Invalidate(range); + TimeRange ar = range.Intersected(GetAudioCacheRange()); + if (ar.length() != 0) { + audio_playback_cache()->Invalidate(ar); } } diff --git a/app/node/node.h b/app/node/node.h index d123c0e72..dd38040d4 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -231,6 +231,9 @@ public: return audio_cache_; } + virtual TimeRange GetVideoCacheRange() const { return TimeRange(); } + virtual TimeRange GetAudioCacheRange() const { return TimeRange(); } + struct Position { Position(const QPointF &p = QPointF(0, 0), bool e = false) diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index b0ba3ec8c..2532fa3ae 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -147,6 +147,16 @@ public: return timeline_points_; } + virtual TimeRange GetVideoCacheRange() const override + { + return TimeRange(0, GetVideoLength()); + } + + virtual TimeRange GetAudioCacheRange() const override + { + return TimeRange(0, GetAudioLength()); + } + QVector GetEnabledStreamsAsReferences() const; QVector GetEnabledVideoStreams() const; diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index 859ce454e..d02311cef 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -161,6 +161,10 @@ void AudioPlaybackCache::WriteWaveform(const TimeRange &range, const TimeRangeLi visual_.OverwriteSilence(r.in(), r.length()); } } + + if (!valid_ranges.isEmpty()) { + emit WaveformUpdated(); + } } void AudioPlaybackCache::WriteSilence(const TimeRange &range) @@ -170,6 +174,11 @@ void AudioPlaybackCache::WriteSilence(const TimeRange &range) WritePCM(range, {range}, SampleBuffer()); } +void AudioPlaybackCache::TrimIn(const rational &in) +{ + visual_.TrimIn(in); +} + AudioPlaybackCache::Segment AudioPlaybackCache::CloneSegment(const AudioPlaybackCache::Segment &s) const { Segment new_seg = s; diff --git a/app/render/audioplaybackcache.h b/app/render/audioplaybackcache.h index 164953418..984135913 100644 --- a/app/render/audioplaybackcache.h +++ b/app/render/audioplaybackcache.h @@ -72,6 +72,8 @@ public: void WriteSilence(const TimeRange &range); + void TrimIn(const rational &in); + class Segment { public: @@ -200,14 +202,14 @@ public: */ PlaybackDevice* CreatePlaybackDevice(QObject *parent = nullptr) const; - const AudioVisualWaveform &visual() const - { - return visual_; - } + const AudioVisualWaveform &visual() const { return visual_; } + void set_visual(const AudioVisualWaveform &v) { visual_ = v; } signals: void ParametersChanged(); + void WaveformUpdated(); + private: static const qint64 kDefaultSegmentSizePerChannel; diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index d12325c75..2fd2e046f 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -27,7 +27,7 @@ namespace olive { -void PlaybackCache::Invalidate(const TimeRange &r, bool signal) +void PlaybackCache::Invalidate(const TimeRange &r) { if (r.in() == r.out()) { qWarning() << "Tried to invalidate zero-length range"; @@ -38,8 +38,10 @@ void PlaybackCache::Invalidate(const TimeRange &r, bool signal) InvalidateEvent(r); - if (signal) { - emit Invalidated(r); + emit Invalidated(r); + + if (automatic_) { + emit Request(r, false); } } @@ -73,11 +75,20 @@ Project *PlaybackCache::GetProject() const PlaybackCache::PlaybackCache(QObject *parent) : QObject(parent), - enabled_(false) + automatic_(false) { uuid_ = QUuid::createUuid(); } +void PlaybackCache::SetIsAutomatic(bool e) +{ + if (automatic_ != e) { + automatic_ = e; + + emit AutomaticChanged(automatic_); + } +} + TimeRangeList PlaybackCache::GetInvalidatedRanges(TimeRange intersecting) { TimeRangeList invalidated; diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index b10b48686..83c4365da 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -42,14 +42,8 @@ public: const QUuid &GetUuid() const { return uuid_; } void SetUuid(const QUuid &u) { uuid_ = u; } - bool IsEnabled() const { return enabled_; } - void SetEnabled(bool e) - { - if (enabled_ != e) { - enabled_ = e; - emit EnabledChanged(e); - } - } + bool IsAutomatic() const { return automatic_; } + void SetIsAutomatic(bool e); TimeRangeList GetInvalidatedRanges(TimeRange intersecting); TimeRangeList GetInvalidatedRanges(const rational &length) @@ -65,7 +59,7 @@ public: QString GetCacheDirectory() const; - void Invalidate(const TimeRange& r, bool signal = true); + void Invalidate(const TimeRange& r); const TimeRangeList &GetValidatedRanges() const { return validated_; } @@ -79,7 +73,9 @@ signals: void Validated(const olive::TimeRange& r); - void EnabledChanged(bool e); + void Request(const olive::TimeRange& r, bool previews_only); + + void AutomaticChanged(bool e); protected: void Validate(const TimeRange& r, bool signal = true); @@ -93,7 +89,7 @@ private: QUuid uuid_; - bool enabled_; + bool automatic_; }; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index f697a6aca..107c32acf 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -33,14 +33,11 @@ namespace olive { -// We may want to make this configurable at some point, so for now this constant is used as a -// placeholder for where that configarable variable would be used. -const bool PreviewAutoCacher::kRealTimeWaveformsEnabled = true; - -PreviewAutoCacher::PreviewAutoCacher() : +PreviewAutoCacher::PreviewAutoCacher(QObject *parent) : + QObject(parent), viewer_node_(nullptr), use_custom_range_(false), - pause_audio_(false), + pause_renders_(false), single_frame_render_(nullptr) { // Set defaults @@ -49,7 +46,7 @@ PreviewAutoCacher::PreviewAutoCacher() : // Wait a certain amount of time before requeuing when we receive an invalidate signal delayed_requeue_timer_.setInterval(OLIVE_CONFIG("AutoCacheDelay").toInt()); delayed_requeue_timer_.setSingleShot(true); - connect(&delayed_requeue_timer_, &QTimer::timeout, this, &PreviewAutoCacher::RequeueFrames); + connect(&delayed_requeue_timer_, &QTimer::timeout, this, &PreviewAutoCacher::TryRender); // Catch when a conform is ready connect(ConformManager::instance(), &ConformManager::ConformReady, this, &PreviewAutoCacher::ConformFinished); @@ -84,28 +81,18 @@ RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, RenderTicket return RenderAudio(range, false, priority); } -void PreviewAutoCacher::VideoInvalidated(const TimeRange &range) +void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range) { - // Stop any current render tasks because a) they might be out of date now anyway, and b) we - // want to dedicate all our rendering power to realtime feedback for the user - CancelVideoTasks(); + FrameHashCache *cache = static_cast(sender()); - // If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames - if (viewer_node_->video_frame_cache()->IsEnabled() && !NodeInputDragger::IsInputBeingDragged()) { - StartCachingVideoRange(range); - } + VideoInvalidatedFromNode(cache->parent(), range); } -void PreviewAutoCacher::AudioInvalidated(const TimeRange &range) +void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range) { - // We don't stop rendering audio because currently there's no system of requeuing audio if it's - // cancelled, so some areas may end up unrendered forever - // ClearAudioQueue(); + AudioPlaybackCache *cache = static_cast(sender()); - // If we're auto-caching audio or require realtime waveforms, we'll have to render this - if (viewer_node_->audio_playback_cache()->IsEnabled() || kRealTimeWaveformsEnabled) { - StartCachingAudioRange(range); - } + AudioInvalidatedFromNode(cache->parent(), range); } void PreviewAutoCacher::AudioRendered() @@ -118,67 +105,38 @@ void PreviewAutoCacher::AudioRendered() if (audio_tasks_.contains(watcher)) { // Assume that a "result" is a fully completed image and a non-result is a cancelled ticket TimeRange range = audio_tasks_.take(watcher); + Node *node = Node::ValueToPtr(watcher->property("node")); if (watcher->HasResult()) { - // Remove this task from the list + AudioCacheData &d = audio_cache_data_[node]; + JobTime watcher_job_time = watcher->property("job").value(); - TimeRangeList valid_ranges = audio_job_tracker_.getCurrentSubRanges(range, watcher_job_time); + TimeRangeList valid_ranges = d.job_tracker.getCurrentSubRanges(range, watcher_job_time); AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value(); - if (viewer_node_->audio_playback_cache()->IsEnabled()) { + SampleBuffer buf = watcher->Get().value(); + node->audio_playback_cache()->SetParameters(buf.audio_params()); + /*if (node->audio_playback_cache()->IsEnabled()) { // WritePCM is tolerant to its buffer being null, it will just write silence instead - viewer_node_->audio_playback_cache()->WritePCM(range, + node->audio_playback_cache()->WritePCM(range, valid_ranges, watcher->Get().value()); - } - - viewer_node_->audio_playback_cache()->WriteWaveform(range, valid_ranges, &waveform); + }*/ // Detect if this audio was incomplete because it was waiting on a conform to finish if (watcher->GetTicket()->property("incomplete").toBool()) { if (last_conform_task_ > watcher_job_time) { // Requeue now - viewer_node_->audio_playback_cache()->Invalidate(range); + node->audio_playback_cache()->Invalidate(range); } else { // Wait for conform - audio_needing_conform_.insert(range); - } - } else{ - // Retrieve visual waveforms - QVector waveform_list = watcher->GetTicket()->property("waveforms").value< QVector >(); - foreach (const RenderProcessor::RenderedWaveform& waveform_info, waveform_list) { - // Find original track - ClipBlock* block = nullptr; - - for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) { - if (it.value() == waveform_info.block) { - block = static_cast(it.key()); - break; - } - } - - if (block && !valid_ranges.isEmpty()) { - // Generate visual waveform in this background thread - block->waveform().set_channel_count(viewer_node_->GetAudioParams().channel_count()); - - // Determine which of the waveform ranges we got intersects with the valid ranges - TimeRangeList intersections = valid_ranges.Intersects(waveform_info.range + block->in()); - foreach (TimeRange r, intersections) { - // For each range, adjust it relative to the block and write it - r -= block->in(); - - if (waveform_info.silence) { - block->waveform().OverwriteSilence(r.in(), r.length()); - } else { - block->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length()); - } - } - - emit block->PreviewChanged(); - } + d.needing_conform.insert(range); } + } else { + qDebug() << "Writing waveforms to" << range << valid_ranges; + node->audio_playback_cache()->WriteWaveform(range, valid_ranges, &waveform); } } @@ -352,53 +310,64 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) void PreviewAutoCacher::ConnectToNodeCache(Node *node) { - // TEMP: Retain existing behavior until more work is done - if (node == viewer_node_) { - connect(node->video_frame_cache(), - &PlaybackCache::EnabledChanged, - this, - &PreviewAutoCacher::VideoAutoCacheEnableChanged); + connect(node->video_frame_cache(), + &PlaybackCache::AutomaticChanged, + this, + &PreviewAutoCacher::VideoAutoCacheEnableChanged); - connect(node->audio_playback_cache(), - &PlaybackCache::EnabledChanged, - this, - &PreviewAutoCacher::AudioAutoCacheEnableChanged); + connect(node->audio_playback_cache(), + &PlaybackCache::AutomaticChanged, + this, + &PreviewAutoCacher::AudioAutoCacheEnableChanged); - connect(node->video_frame_cache(), - &PlaybackCache::Invalidated, - this, - &PreviewAutoCacher::VideoInvalidated); + connect(node->video_frame_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::VideoInvalidatedFromCache); - connect(node->audio_playback_cache(), - &PlaybackCache::Invalidated, - this, - &PreviewAutoCacher::AudioInvalidated); + connect(node->audio_playback_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::AudioInvalidatedFromCache); + + // Copy invalidated ranges and start rendering if necessary + if (node->video_frame_cache()->IsAutomatic()) { + VideoAutoCacheEnableChangedFromNode(node, true); + } + + if (node->audio_playback_cache()->IsAutomatic()) { + AudioAutoCacheEnableChangedFromNode(node, true); } } void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) { - // TEMP: Retain existing behavior until more work is done - if (node == viewer_node_) { - disconnect(node->video_frame_cache(), - &PlaybackCache::EnabledChanged, - this, - &PreviewAutoCacher::VideoAutoCacheEnableChanged); + disconnect(node->video_frame_cache(), + &PlaybackCache::AutomaticChanged, + this, + &PreviewAutoCacher::VideoAutoCacheEnableChanged); - disconnect(node->audio_playback_cache(), - &PlaybackCache::EnabledChanged, - this, - &PreviewAutoCacher::AudioAutoCacheEnableChanged); + disconnect(node->audio_playback_cache(), + &PlaybackCache::AutomaticChanged, + this, + &PreviewAutoCacher::AudioAutoCacheEnableChanged); - disconnect(node->video_frame_cache(), - &PlaybackCache::Invalidated, - this, - &PreviewAutoCacher::VideoInvalidated); + disconnect(node->video_frame_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::VideoInvalidatedFromCache); - disconnect(node->audio_playback_cache(), - &PlaybackCache::Invalidated, - this, - &PreviewAutoCacher::AudioInvalidated); + disconnect(node->audio_playback_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::AudioInvalidatedFromCache); + + if (node->video_frame_cache()->IsAutomatic()) { + VideoAutoCacheEnableChangedFromNode(node, false); + } + + if (node->audio_playback_cache()->IsAutomatic()) { + AudioAutoCacheEnableChangedFromNode(node, false); } } @@ -421,17 +390,17 @@ void PreviewAutoCacher::CancelQueuedSingleFrameRender() } } -void PreviewAutoCacher::VideoInvalidatedList(const TimeRangeList &list) +void PreviewAutoCacher::VideoInvalidatedList(Node *node, const TimeRangeList &list) { foreach (const TimeRange &range, list) { - VideoInvalidated(range); + VideoInvalidatedFromNode(node, range); } } -void PreviewAutoCacher::AudioInvalidatedList(const TimeRangeList &list) +void PreviewAutoCacher::AudioInvalidatedList(Node *node, const TimeRangeList &list) { foreach (const TimeRange &range, list) { - AudioInvalidated(range); + AudioInvalidatedFromNode(node, range); } } @@ -441,24 +410,68 @@ void PreviewAutoCacher::StartCachingRange(const TimeRange &range, TimeRangeList tracker->insert(range, graph_changed_time_); } -void PreviewAutoCacher::StartCachingVideoRange(const TimeRange &range) +void PreviewAutoCacher::StartCachingVideoRange(Node *node, const TimeRange &range) { - StartCachingRange(range, &invalidated_video_, &video_job_tracker_); - RequeueFrames(); + VideoCacheData &d = video_cache_data_[node]; + + StartCachingRange(range, &d.invalidated, &d.job_tracker); + TryRender(); } -void PreviewAutoCacher::StartCachingAudioRange(const TimeRange &range) +void PreviewAutoCacher::StartCachingAudioRange(Node *node, const TimeRange &range) { - StartCachingRange(range, &invalidated_audio_, &audio_job_tracker_); + AudioCacheData &d = audio_cache_data_[node]; + + StartCachingRange(range, &d.invalidated, &d.job_tracker); TryRender(); } +void PreviewAutoCacher::VideoInvalidatedFromNode(Node *node, const TimeRange &range) +{ + // Stop any current render tasks because a) they might be out of date now anyway, and b) we + // want to dedicate all our rendering power to realtime feedback for the user + CancelVideoTasks(node); + + // If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames + if (!NodeInputDragger::IsInputBeingDragged()) { + StartCachingVideoRange(node, range); + } +} + +void PreviewAutoCacher::AudioInvalidatedFromNode(Node *node, const TimeRange &range) +{ + // We don't stop rendering audio because currently there's no system of requeuing audio if it's + // cancelled, so some areas may end up unrendered forever + // ClearAudioQueue(); + + // If we're auto-caching audio or require realtime waveforms, we'll have to render this + StartCachingAudioRange(node, range); +} + +void PreviewAutoCacher::VideoAutoCacheEnableChangedFromNode(Node *node, bool e) +{ + if (e) { + VideoInvalidatedList(node, node->video_frame_cache()->GetInvalidatedRanges(node->GetVideoCacheRange())); + } else { + CancelVideoTasks(node); + } +} + +void PreviewAutoCacher::AudioAutoCacheEnableChangedFromNode(Node *node, bool e) +{ + if (e) { + AudioInvalidatedList(node, node->audio_playback_cache()->GetInvalidatedRanges(node->GetAudioCacheRange())); + } else { + CancelAudioTasks(node); + } +} + void PreviewAutoCacher::SetPlayhead(const rational &playhead) { cache_range_ = TimeRange(playhead - OLIVE_CONFIG("DiskCacheBehind").value(), playhead + OLIVE_CONFIG("DiskCacheAhead").value()); - RequeueFrames(); + TryRender(); } template @@ -487,9 +500,15 @@ void PreviewAutoCacher::CancelAudioTasks(bool and_wait_for_them_to_finish) CancelTasks(audio_tasks_, and_wait_for_them_to_finish); } -void PreviewAutoCacher::SetAudioPaused(bool e) +bool PreviewAutoCacher::IsRenderingCustomRange() const { - pause_audio_ = e; + const VideoCacheData &d = video_cache_data_.value(viewer_node_); + return d.iterator.IsCustomRange() && d.iterator.HasNext(); +} + +void PreviewAutoCacher::SetRendersPaused(bool e) +{ + pause_renders_ = e; if (!e) { TryRender(); } @@ -533,6 +552,12 @@ void PreviewAutoCacher::ValueHintChanged(const NodeInput &input) void PreviewAutoCacher::TryRender() { + delayed_requeue_timer_.stop(); + + if (pause_renders_) { + return; + } + if (!graph_update_queue_.isEmpty()) { // 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 @@ -546,24 +571,6 @@ void PreviewAutoCacher::TryRender() ProcessUpdateQueue(); } - // Check for newly invalidated video and hash it - if (!invalidated_video_.isEmpty()) { - if (!copied_viewer_node_->GetConnectedTextureOutput()) { - queued_frame_iterator_.reset(); - } else if (queued_frame_iterator_.HasNext()) { - queued_frame_iterator_.insert(invalidated_video_); - } else { - queued_frame_iterator_ = TimeRangeListFrameIterator(invalidated_video_, viewer_node_->GetVideoParams().frame_rate_as_time_base()); - } - invalidated_video_.clear(); - } - - if (!invalidated_audio_.isEmpty()) { - // Add newly invalidated audio to iterator - audio_iterator_.insert(invalidated_audio_); - invalidated_audio_.clear(); - } - if (single_frame_render_) { // Check if already caching this RenderTicketWatcher *watcher = RenderFrame(single_frame_render_->property("time").value(), @@ -578,36 +585,62 @@ void PreviewAutoCacher::TryRender() const int max_tasks = RenderManager::GetNumberOfIdealConcurrentJobs(); // Handle video tasks - rational t; - while (video_tasks_.size() < max_tasks && queued_frame_iterator_.GetNext(&t)) { - RenderTicketWatcher* render_task = video_tasks_.key(t); + for (auto it=video_cache_data_.begin(); it!=video_cache_data_.end(); it++) { + VideoCacheData &d = it.value(); - // We want this hash, if we're not already rendering, start render now - if (!render_task) { - // Don't render any hash more than once - RenderFrame(t, RenderTicketPriority::kNormal, viewer_node_->video_frame_cache()); + // Check for newly invalidated video + if (!d.invalidated.isEmpty()) { + if (d.iterator.HasNext()) { + d.iterator.insert(d.invalidated); + } else { + d.iterator = TimeRangeListFrameIterator(d.invalidated, viewer_node_->GetVideoParams().frame_rate_as_time_base()); + } + d.invalidated.clear(); } - emit SignalCacheProxyTaskProgress(double(queued_frame_iterator_.frame_index()) / double(queued_frame_iterator_.size())); + // Queue next frames + rational t; + while (video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { + RenderTicketWatcher* render_task = video_tasks_.key(t); - if (!queued_frame_iterator_.HasNext()) { - emit StopCacheProxyTasks(); + // We want this hash, if we're not already rendering, start render now + if (!render_task) { + // Don't render any hash more than once + RenderFrame(it.key(), t, RenderTicketPriority::kNormal, it.key()->video_frame_cache()); + } + + emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); + + if (!d.iterator.HasNext()) { + emit StopCacheProxyTasks(); + } } } + // Handle audio tasks - while (!audio_iterator_.isEmpty() && audio_tasks_.size() < max_tasks && !pause_audio_) { - // Copy first range in list - TimeRange r = audio_iterator_.first(); + for (auto it=audio_cache_data_.begin(); it!=audio_cache_data_.end(); it++) { + AudioCacheData &d = it.value(); - // Limit to the minimum sample rate supported by AudioVisualWaveform - we use this value so that - // whatever chunk we render can be summed down to the smallest mipmap whole - r.set_out(qMin(r.out(), r.in() + AudioVisualWaveform::kMinimumSampleRate.flipped())); + if (!d.invalidated.isEmpty()) { + // Add newly invalidated audio to iterator + d.iterator.insert(d.invalidated); + d.invalidated.clear(); + } - // Start job - RenderAudio(r, true, RenderTicketPriority::kNormal); + while (!d.iterator.isEmpty() && audio_tasks_.size() < max_tasks) { + // Copy first range in list + TimeRange r = d.iterator.first(); - audio_iterator_.remove(r); + // Limit to the minimum sample rate supported by AudioVisualWaveform - we use this value so that + // whatever chunk we render can be summed down to the smallest mipmap whole + r.set_out(qMin(r.out(), r.in() + AudioVisualWaveform::kMinimumSampleRate.flipped())); + + // Start job + RenderAudio(it.key(), r, true, RenderTicketPriority::kNormal); + + d.iterator.remove(r); + } } } @@ -616,6 +649,9 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); watcher->setProperty("cache", Node::PtrToValue(cache)); + if (cache) { + cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); + } connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered); video_tasks_.insert(watcher, time); watcher->SetTicket(RenderManager::instance()->RenderFrame(node, @@ -632,8 +668,11 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, bool generate_waveforms, RenderTicketPriority priority) { + qDebug() << "Rendering" << r << "for" << node; + RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); + watcher->setProperty("node", Node::PtrToValue(node)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); audio_tasks_.insert(watcher, r); @@ -642,75 +681,45 @@ RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, b return ticket; } -void PreviewAutoCacher::RequeueFrames() -{ - delayed_requeue_timer_.stop(); - - if (viewer_node_ - && (viewer_node_->video_frame_cache()->IsEnabled() || use_custom_range_) - && viewer_node_->video_frame_cache()->HasInvalidatedRanges(viewer_node_->GetVideoLength()) - && !IsRenderingCustomRange()) { - TimeRange using_range = use_custom_range_ ? custom_autocache_range_ : cache_range_; - - TimeRangeList invalidated = viewer_node_->video_frame_cache()->GetInvalidatedRanges(using_range); - queued_frame_iterator_ = TimeRangeListFrameIterator(invalidated, viewer_node_->video_frame_cache()->GetTimebase()); - - queued_frame_iterator_.SetCustomRange(use_custom_range_); - - emit StopCacheProxyTasks(); - - if (use_custom_range_) { - CustomCacheTask *cct = new CustomCacheTask(viewer_node_->GetLabelOrName()); - connect(this, &PreviewAutoCacher::StopCacheProxyTasks, cct, &CustomCacheTask::Finish); - connect(this, &PreviewAutoCacher::SignalCacheProxyTaskProgress, cct, &CustomCacheTask::ProgressChanged); - connect(cct, &CustomCacheTask::Cancelled, this, &PreviewAutoCacher::CacheProxyTaskCancelled); - TaskManager::instance()->AddTask(cct); - } - - use_custom_range_ = false; - - TryRender(); - } -} - void PreviewAutoCacher::ConformFinished() { // Got an audio conform, requeue all the audio currently needing a conform last_conform_task_.Acquire(); - if (!audio_needing_conform_.isEmpty()) { - // This list should be empty if there was a viewer switch - foreach (const TimeRange &range, audio_needing_conform_) { - viewer_node_->audio_playback_cache()->Invalidate(range); + for (auto it=audio_cache_data_.begin(); it!=audio_cache_data_.end(); it++) { + AudioCacheData &d = it.value(); + + if (!d.needing_conform.isEmpty()) { + // This list should be empty if there was a viewer switch + foreach (const TimeRange &range, d.needing_conform) { + it.key()->audio_playback_cache()->Invalidate(range); + } + d.needing_conform.clear(); } - audio_needing_conform_.clear(); } } void PreviewAutoCacher::VideoAutoCacheEnableChanged(bool e) { - if (e) { - VideoInvalidatedList(viewer_node_->video_frame_cache()->GetInvalidatedRanges(viewer_node_->GetVideoLength())); - } else { - CancelVideoTasks(); - queued_frame_iterator_.reset(); - } + FrameHashCache *cache = static_cast(sender()); + + VideoAutoCacheEnableChangedFromNode(cache->parent(), e); } void PreviewAutoCacher::AudioAutoCacheEnableChanged(bool e) { - if (e) { - AudioInvalidatedList(viewer_node_->audio_playback_cache()->GetInvalidatedRanges(viewer_node_->GetAudioLength())); - } else { - CancelAudioTasks(); - audio_iterator_.clear(); - } + AudioPlaybackCache *cache = static_cast(sender()); + + AudioAutoCacheEnableChangedFromNode(cache->parent(), e); } void PreviewAutoCacher::CacheProxyTaskCancelled() { - queued_frame_iterator_.reset(); - RequeueFrames(); + for (auto it=video_cache_data_.begin(); it!=video_cache_data_.end(); it++) { + it->iterator.reset(); + } + + TryRender(); } void PreviewAutoCacher::ForceCacheRange(const TimeRange &range) @@ -719,7 +728,7 @@ void PreviewAutoCacher::ForceCacheRange(const TimeRange &range) custom_autocache_range_ = range; // Re-hash these frames and start rendering - StartCachingVideoRange(range); + StartCachingVideoRange(viewer_node_, range); } void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) @@ -747,23 +756,12 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) CancelAudioTasks(true); } - // Clear iterators - queued_frame_iterator_.reset(); - audio_iterator_.clear(); - - // Clear any invalidated ranges - invalidated_video_.clear(); - invalidated_audio_.clear(); - // Clear any single frame render that might be queued CancelQueuedSingleFrameRender(); // Not interested in video passthroughs anymore video_immediate_passthroughs_.clear(); - // Not interested in audio conforming anymore - audio_needing_conform_.clear(); - // Disconnect from all node cache's for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) { DisconnectFromNodeCache(it.key()); @@ -775,8 +773,10 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) copy_map_.clear(); copied_viewer_node_ = nullptr; graph_update_queue_.clear(); - video_job_tracker_.clear(); - audio_job_tracker_.clear(); + + // Ensure all cache data is cleared + video_cache_data_.clear(); + audio_cache_data_.clear(); // Disconnect signals for future node additions/deletions NodeGraph* graph = viewer_node_->parent(); @@ -795,6 +795,8 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) // Copy graph NodeGraph* graph = viewer_node_->parent(); + SetRendersPaused(true); + // Add all nodes for (int i=0; inodes().at(i), copied_project_.nodes().at(i)); @@ -826,9 +828,7 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) connect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged, Qt::DirectConnection); connect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged, Qt::DirectConnection); - // Copy invalidated ranges and start rendering if necessary - VideoInvalidatedList(viewer_node_->video_frame_cache()->GetInvalidatedRanges(viewer_node_->GetVideoLength())); - AudioInvalidatedList(viewer_node_->audio_playback_cache()->GetInvalidatedRanges(viewer_node_->GetAudioLength())); + SetRendersPaused(false); } } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 4c20a72d6..0572bbd31 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -47,7 +47,7 @@ class PreviewAutoCacher : public QObject { Q_OBJECT public: - PreviewAutoCacher(); + PreviewAutoCacher(QObject *parent = nullptr); virtual ~PreviewAutoCacher() override; @@ -85,12 +85,9 @@ public: void CancelVideoTasks(bool and_wait_for_them_to_finish = false); void CancelAudioTasks(bool and_wait_for_them_to_finish = false); - bool IsRenderingCustomRange() const - { - return queued_frame_iterator_.IsCustomRange() && queued_frame_iterator_.HasNext(); - } + bool IsRenderingCustomRange() const; - void SetAudioPaused(bool e); + void SetRendersPaused(bool e); signals: void StopCacheProxyTasks(); @@ -137,12 +134,18 @@ private: void CancelQueuedSingleFrameRender(); - void VideoInvalidatedList(const TimeRangeList &list); - void AudioInvalidatedList(const TimeRangeList &list); + void VideoInvalidatedList(Node *node, const TimeRangeList &list); + void AudioInvalidatedList(Node *node, const TimeRangeList &list); void StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker); - void StartCachingVideoRange(const TimeRange &range); - void StartCachingAudioRange(const TimeRange &range); + void StartCachingVideoRange(Node *node, const TimeRange &range); + void StartCachingAudioRange(Node *node, const TimeRange &range); + + void VideoInvalidatedFromNode(Node *node, const olive::TimeRange &range); + void AudioInvalidatedFromNode(Node *node, const olive::TimeRange &range); + + void VideoAutoCacheEnableChangedFromNode(Node *node, bool e); + void AudioAutoCacheEnableChangedFromNode(Node *node, bool e); class QueuedJob { public: @@ -177,15 +180,9 @@ private: bool use_custom_range_; TimeRange custom_autocache_range_; - TimeRangeList invalidated_video_; - TimeRangeList invalidated_audio_; - - bool pause_audio_; + bool pause_renders_; RenderTicketPtr single_frame_render_; - - QMap audio_tasks_; - QMap video_tasks_; QMap > video_immediate_passthroughs_; JobTime graph_changed_time_; @@ -193,28 +190,37 @@ private: QTimer delayed_requeue_timer_; - TimeRangeList audio_needing_conform_; - JobTime last_conform_task_; - RenderJobTracker video_job_tracker_; - RenderJobTracker audio_job_tracker_; + QMap audio_tasks_; + QMap video_tasks_; - TimeRangeListFrameIterator queued_frame_iterator_; - TimeRangeList audio_iterator_; + struct VideoCacheData { + TimeRangeList invalidated; + RenderJobTracker job_tracker; + TimeRangeListFrameIterator iterator; + }; - static const bool kRealTimeWaveformsEnabled; + struct AudioCacheData { + TimeRangeList invalidated; + TimeRangeList needing_conform; + RenderJobTracker job_tracker; + TimeRangeList iterator; + }; + + QHash video_cache_data_; + QHash audio_cache_data_; private slots: /** * @brief Handler for when the NodeGraph reports a video change over a certain time range */ - void VideoInvalidated(const olive::TimeRange &range); + void VideoInvalidatedFromCache(const olive::TimeRange &range); /** * @brief Handler for when the NodeGraph reports a audio change over a certain time range */ - void AudioInvalidated(const olive::TimeRange &range); + void AudioInvalidatedFromCache(const olive::TimeRange &range); /** * @brief Handler for when the RenderManager has returned rendered audio @@ -241,7 +247,7 @@ private slots: /** * @brief Generic function called whenever the frames to render need to be (re)queued */ - void RequeueFrames(); + //void RequeueFrames(); void ConformFinished(); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 0b2d469b8..e1c72dd79 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -365,26 +365,6 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim NodeValueTable::Merge({merged_table, table}); } - - // Create block waveforms if requested - if (ticket_->property("enablewaveforms").toBool() && clip_cast) { - // Format information for use in the main thread - RenderedWaveform waveform_info; - waveform_info.block = clip_cast; - waveform_info.range = range_for_block - b->in(); - - if (!(waveform_info.silence = !samples_from_this_block.is_allocated())) { - // Generate a visual waveform from the samples acquired from this block - AudioVisualWaveform visual_waveform; - visual_waveform.set_channel_count(audio_params.channel_count()); - visual_waveform.OverwriteSamples(samples_from_this_block, audio_params.sample_rate()); - waveform_info.waveform = visual_waveform; - } - - QVector waveform_list = ticket_->property("waveforms").value< QVector >(); - waveform_list.append(waveform_info); - ticket_->setProperty("waveforms", QVariant::fromValue(waveform_list)); - } } } diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 693ac109c..d0e066493 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -406,7 +406,7 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, bool multitrim_enabled = IsClipTrimmable(clicked_item, clips, trim_mode); // Create ghosts for trimming - foreach (Block* clip_item, clips) { + for (Block* clip_item : clips) { if (clip_item != clicked_item && (!multitrim_enabled || !IsClipTrimmable(clip_item, clips, trim_mode))) { // Either multitrim is disabled or this clip is NOT the earliest/latest in its track. We @@ -481,7 +481,7 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, // I'm only including it to prevent any potentially unintended behavior. if (clips.size() == 1 && !(modifiers & Qt::AltModifier)) { if (ClipBlock *adjacent_clip = dynamic_cast(adjacent)) { - foreach (Block *adjacent_link, adjacent_clip->block_links()) { + for (Block *adjacent_link : adjacent_clip->block_links()) { adjacent_ghosts.append(AddGhostFromBlock(adjacent_link, flipped_mode)); } } @@ -496,7 +496,7 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, // expected to fill the remaining space (no gap needs to be created) ghost->SetData(TimelineViewGhostItem::kTrimIsARollEdit, static_cast(adjacent)); - foreach (TimelineViewGhostItem *adjacent_ghost, adjacent_ghosts) { + for (TimelineViewGhostItem *adjacent_ghost : adjacent_ghosts) { if (adjacent_ghost) { if (treat_trim_as_slide) { // We're sliding a transition rather than a pure trim/roll @@ -699,7 +699,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) // Place the copy instead of the original block block = static_cast(Node::CopyNodeInGraph(block, command)); if (ClipBlock *new_clip = dynamic_cast(block)) { - new_clip->waveform() = static_cast(p.block)->waveform(); + new_clip->set_waveform(static_cast(p.block)->waveform()); } } diff --git a/app/widget/timelinewidget/undo/timelineundosplit.cpp b/app/widget/timelinewidget/undo/timelineundosplit.cpp index 9d1ea40ce..74626750c 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.cpp +++ b/app/widget/timelinewidget/undo/timelineundosplit.cpp @@ -44,7 +44,7 @@ void BlockSplitCommand::redo() if (ClipBlock *new_clip = dynamic_cast(new_block_)) { ClipBlock *old_clip = static_cast(block_); - new_clip->waveform() = old_clip->waveform(); + new_clip->set_waveform(old_clip->waveform()); } // Determine our new lengths diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 22c5daa4e..f7fd1a637 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -47,6 +47,7 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : ghosts_(nullptr), show_beam_cursor_(false), connected_track_list_(nullptr), + show_thumbnails_(true), show_waveforms_(true), transition_overlay_out_(nullptr), transition_overlay_in_(nullptr) @@ -519,12 +520,32 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q painter->drawRect(r); if (ClipBlock *clip = dynamic_cast(block)) { + QRect preview_rect = r.adjusted(0, text_total_height, 0, 0).toRect(); + + // Draw clip thumbnails + if (clip->GetTrackType() == Track::kVideo && show_thumbnails_ && preview_rect.height() > r.height()/3) { + const int kTempThumbWidth = 120; + const int kTempThumbHeight = 68; + + QRect thumb_rect; + painter->setClipRect(preview_rect); + for (int i=preview_rect.left(); ifillRect(thumb_rect, Qt::red); + } + painter->setClipping(false); + } + // Draw waveform - if (show_waveforms_) { - QRect waveform_rect = r.adjusted(0, text_total_height, 0, 0).toRect(); - painter->setPen(shadow_color); - AudioVisualWaveform::DrawWaveform(painter, waveform_rect, this->GetScale(), clip->waveform(), - SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in); + if (clip->GetTrackType() == Track::kAudio && show_waveforms_) { + if (const AudioVisualWaveform *wave = clip->waveform()) { + rational waveform_start = SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in; + painter->setPen(shadow_color); + + AudioVisualWaveform::DrawWaveform(painter, preview_rect, this->GetScale(), *wave, waveform_start); + } } // Draw zebra stripes and markers diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 3dc5989b3..28a8cb799 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -153,6 +153,7 @@ private: TrackList* connected_track_list_; + bool show_thumbnails_; bool show_waveforms_; ClipBlock *transition_overlay_out_; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index a32c5df02..5d6980bcb 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -147,6 +147,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) : setAcceptDrops(true); + auto_cacher_ = new PreviewAutoCacher(this); + connect(Core::instance(), &Core::ColorPickerEnabled, this, &ViewerWidget::SetSignalCursorColorEnabled); connect(this, &ViewerWidget::CursorColor, Core::instance(), &Core::ColorPickerColorEmitted); connect(AudioManager::instance(), &AudioManager::OutputParamsChanged, this, &ViewerWidget::UpdateAudioProcessor); @@ -191,7 +193,7 @@ void ViewerWidget::TimeChangedEvent(const rational &time) } // Send time to auto-cacher - auto_cacher_.SetPlayhead(time); + auto_cacher_->SetPlayhead(time); last_time_ = time; } @@ -275,7 +277,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) void ViewerWidget::ConnectedNodeChangeEvent(ViewerOutput *n) { - auto_cacher_.SetViewerNode(n); + auto_cacher_->SetViewerNode(n); display_widget_->SetSubtitleTracks(dynamic_cast(n)); } @@ -369,13 +371,13 @@ void ViewerWidget::SetFullScreen(QScreen *screen) void ViewerWidget::CacheEntireSequence() { - auto_cacher_.ForceCacheRange(TimeRange(0, GetConnectedNode()->GetVideoLength())); + auto_cacher_->ForceCacheRange(TimeRange(0, GetConnectedNode()->GetVideoLength())); } void ViewerWidget::CacheSequenceInOut() { if (GetConnectedNode() && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { - auto_cacher_.ForceCacheRange(GetConnectedNode()->GetTimelinePoints()->workarea()->range()); + auto_cacher_->ForceCacheRange(GetConnectedNode()->GetTimelinePoints()->workarea()->range()); } else { QMessageBox::warning(this, tr("Error"), @@ -442,12 +444,12 @@ void ViewerWidget::SetEmptyImage() void ViewerWidget::UpdateAutoCacher() { - auto_cacher_.SetPlayhead(GetTime()); + auto_cacher_->SetPlayhead(GetTime()); } void ViewerWidget::ClearVideoAutoCacherQueue() { - auto_cacher_.CancelVideoTasks(); + auto_cacher_->CancelVideoTasks(); } void ViewerWidget::DecrementPrequeuedAudio() @@ -549,7 +551,7 @@ void ViewerWidget::QueueNextAudioBuffer() RenderTicketWatcher *watcher = new RenderTicketWatcher(this); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForPlayback); audio_playback_queue_.push_back(watcher); - watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(audio_playback_queue_time_, queue_end), RenderTicketPriority::kHigh)); + watcher->SetTicket(auto_cacher_->GetRangeOfAudio(TimeRange(audio_playback_queue_time_, queue_end), RenderTicketPriority::kHigh)); audio_playback_queue_time_ = queue_end; } @@ -685,7 +687,7 @@ void ViewerWidget::UpdateTextureFromNode() nonqueue_watchers_.append(watcher); // Clear queue because we want this frame more than any others - if (!GetConnectedNode()->video_frame_cache()->IsEnabled() && !auto_cacher_.IsRenderingCustomRange()) { + if (!GetConnectedNode()->video_frame_cache()->IsAutomatic() && !auto_cacher_->IsRenderingCustomRange()) { ClearVideoAutoCacherQueue(); } @@ -716,10 +718,8 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) foreach (ViewerWidget* viewer, instances_) { if (viewer != this) { viewer->PauseInternal(); - viewer->ClearVideoAutoCacherQueue(); + viewer->auto_cacher_->SetRendersPaused(true); } - - viewer->auto_cacher_.SetAudioPaused(true); } // Disarm recording if armed @@ -823,7 +823,7 @@ void ViewerWidget::PauseInternal() UpdateAudioProcessor(); foreach (ViewerWidget* viewer, instances_) { - viewer->auto_cacher_.SetAudioPaused(false); + viewer->auto_cacher_->SetRendersPaused(false); } UpdateTextureFromNode(); @@ -848,7 +848,7 @@ void ViewerWidget::PushScrubbedAudio() RenderTicketWatcher *watcher = new RenderTicketWatcher(); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForScrubbing); - watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval), RenderTicketPriority::kHigh)); + watcher->SetTicket(auto_cacher_->GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval), RenderTicketPriority::kHigh)); } } } @@ -922,7 +922,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t, RenderTicketPriority p if (!QFileInfo::exists(cache_fn)) { // Frame hasn't been cached, start render job - return auto_cacher_.GetSingleFrame(t, priority); + return auto_cacher_->GetSingleFrame(t, priority); } 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 bc998748c..75f4a73e8 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -255,7 +255,7 @@ private: int prequeue_length_; int prequeue_count_; - PreviewAutoCacher auto_cacher_; + PreviewAutoCacher *auto_cacher_; QVector queue_watchers_; From 2090d076fe49cadc397d5196a42d170b20d64afe Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 24 May 2022 16:07:18 -0700 Subject: [PATCH 05/53] use smarter audio cache that can only have partial sections cached --- app/render/audioparams.cpp | 9 +- app/render/audioparams.h | 1 + app/render/audioplaybackcache.cpp | 418 +++--------------------------- app/render/audioplaybackcache.h | 152 +---------- app/render/framehashcache.cpp | 2 +- app/render/playbackcache.cpp | 10 + app/render/playbackcache.h | 4 + app/render/previewautocacher.cpp | 11 +- 8 files changed, 75 insertions(+), 532 deletions(-) diff --git a/app/render/audioparams.cpp b/app/render/audioparams.cpp index 39680fce8..d5dd635b7 100644 --- a/app/render/audioparams.cpp +++ b/app/render/audioparams.cpp @@ -106,7 +106,14 @@ qint64 AudioParams::samples_to_bytes(const qint64 &samples) const { Q_ASSERT(is_valid()); - return samples * channel_count() * bytes_per_sample_per_channel(); + return samples_to_bytes_per_channel(samples) * channel_count(); +} + +qint64 AudioParams::samples_to_bytes_per_channel(const qint64 &samples) const +{ + Q_ASSERT(is_valid()); + + return samples * bytes_per_sample_per_channel(); } rational AudioParams::samples_to_time(const qint64 &samples) const diff --git a/app/render/audioparams.h b/app/render/audioparams.h index 2f195b645..a58767328 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -213,6 +213,7 @@ public: qint64 time_to_samples(const double& time) const; qint64 time_to_samples(const rational& time) const; qint64 samples_to_bytes(const qint64& samples) const; + qint64 samples_to_bytes_per_channel(const qint64& samples) const; rational samples_to_time(const qint64& samples) const; qint64 bytes_to_samples(const qint64 &bytes) const; rational bytes_to_time(const qint64 &bytes) const; diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index d02311cef..55dba2268 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -39,8 +39,6 @@ AudioPlaybackCache::AudioPlaybackCache(QObject* parent) : AudioPlaybackCache::~AudioPlaybackCache() { - // Segments are volatile, so delete them here - ClearPlaylist(); } void AudioPlaybackCache::SetParameters(const AudioParams ¶ms) @@ -52,102 +50,16 @@ void AudioPlaybackCache::SetParameters(const AudioParams ¶ms) params_ = params; visual_.set_channel_count(params_.channel_count()); - // Restart empty file so there's always "something" to play - ClearPlaylist(); - emit ParametersChanged(); } void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, const SampleBuffer &samples) { - // Ensure if we have enough segments to write this data, creating more if not - qint64 length_diff = params_.time_to_bytes_per_channel(range.out()) - playlist_.GetLength(); - while (length_diff > 0) { - qint64 seg_sz = qMin(kDefaultSegmentSizePerChannel, length_diff); - playlist_.push_back(CreateSegment(seg_sz, playlist_.GetLength())); - length_diff -= seg_sz; - } - - // Keep track of validated ranges so we can signal them all at once at the end - TimeRangeList ranges_we_validated; - - // Calculate buffer size per channel - qint64 buffer_size_per_channel = samples.sample_count() * params_.bytes_per_sample_per_channel(); - - // Write each valid range to the segments - foreach (const TimeRange& r, valid_ranges) { - rational this_segment_in = 0; - - // Write PCM to playlist - for (auto it=playlist_.begin(); it!=playlist_.end(); it++) { - rational this_segment_out = this_segment_in + params_.bytes_per_channel_to_time((*it).size()); - - if (r.in() < this_segment_out) { - // We'll write at least something to this segment - bool succeeded = true; - - // Calculate how much to write - rational this_write_in_point = qMax(r.in(), this_segment_in); - rational this_write_out_point = qMin(r.out(), this_segment_out); - - for (int i=0; i<(*it).channels(); i++) { - QFile seg_file((*it).filename(i)); - - if (seg_file.open(QFile::ReadWrite)) { - // Calculate what the byte offsets are going to be in this segment file - rational in_point_relative = this_write_in_point - this_segment_in; - qint64 dst_offset = params_.time_to_bytes_per_channel(in_point_relative); - - // Calculate where to retrieve data from in the source buffer - qint64 src_offset = params_.time_to_bytes_per_channel(this_write_in_point - range.in()); - - // Determine how many bytes need to be written - qint64 total_write_length = params_.time_to_bytes_per_channel(this_write_out_point - this_write_in_point); - - // Determine how many bytes we actually have in the source buffer - qint64 possible_write_length = qMin(qMax(qint64(0), buffer_size_per_channel - src_offset), total_write_length); - - // Seek to our start offset - seg_file.seek(dst_offset); - - // If we have source bytes to write, write them here - if (possible_write_length > 0) { - // Assume `samples` is valid if we're here, or else `buffer_size_per_channel` and - // therefore `possible_write_length` will be 0. - seg_file.write(reinterpret_cast(samples.data(i)) + src_offset, possible_write_length); - } - - if (possible_write_length < total_write_length) { - // Fill remaining space with silence - QByteArray s(total_write_length - possible_write_length, 0x00); - seg_file.write(s); - } - - seg_file.close(); - } else { - qWarning() << "Failed to write PCM data to" << seg_file.fileName(); - succeeded = false; - } - } - - if (succeeded) { - ranges_we_validated.insert(TimeRange(this_write_in_point, this_write_out_point)); - } - } - - if (r.out() <= this_segment_out) { - // We've reached the end of this range, we can break out of the loop here - break; - } - - // Each segment is contiguous, so this out will be the next segment's in - this_segment_in = this_segment_out; + for (const TimeRange &r : valid_ranges) { + if (WritePartOfSampleBuffer(samples, r.in(), r.in() - range.in(), r.length())) { + Validate(r); } } - - foreach (const TimeRange& v, ranges_we_validated) { - Validate(v); - } } void AudioPlaybackCache::WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform) @@ -174,316 +86,70 @@ void AudioPlaybackCache::WriteSilence(const TimeRange &range) WritePCM(range, {range}, SampleBuffer()); } -void AudioPlaybackCache::TrimIn(const rational &in) +bool AudioPlaybackCache::WritePartOfSampleBuffer(const SampleBuffer &samples, const rational &write_start, const rational &buffer_start, const rational &length) { - visual_.TrimIn(in); -} + qint64 length_in_bytes = params_.time_to_bytes_per_channel(length); -AudioPlaybackCache::Segment AudioPlaybackCache::CloneSegment(const AudioPlaybackCache::Segment &s) const -{ - Segment new_seg = s; + qint64 start_cache_offset = params_.time_to_bytes_per_channel(write_start); + qint64 end_cache_offset = start_cache_offset + length_in_bytes; - new_seg.set_channels(s.channels()); + qint64 start_buffer_offset = params_.time_to_bytes_per_channel(buffer_start); + qint64 end_buffer_offset = std::min(start_buffer_offset + length_in_bytes, params_.samples_to_bytes_per_channel(samples.sample_count())); - // Copy data to a new file - for (int i=0; igenerate(); - new_seg_filename = cache_dir.filePath(QStringLiteral("%1.pcm").arg(r)); - } while (QFileInfo::exists(new_seg_filename)); - - return new_seg_filename; -} - -void AudioPlaybackCache::TrimSegmentIn(AudioPlaybackCache::Segment *s, qint64 new_length) -{ - // Read filename - for (int i=0; ichannels(); i++) { - QFile f(s->filename(i)); - if (f.open(QFile::ReadWrite)) { - // Read segment into memory, according to the size we acknowledge - QByteArray data = f.read(s->size()); - - // Trim to new length - data = data.right(new_length); - - // Seek to start and write - f.seek(0); - - // Write trimmed data - f.write(data); - - f.close(); - } - } - - s->set_size(new_length); -} - -void AudioPlaybackCache::TrimSegmentOut(AudioPlaybackCache::Segment *s, qint64 new_length) -{ - // For efficiency, we don't truncate the file, we just truncate our usage of it - s->set_size(new_length); -} - -void AudioPlaybackCache::RemoveSegmentFromArray(int index) -{ - const Segment &s = playlist_.at(index); - for (int i=0; i(this->parent())) { - d->SetDataLimit(params_.time_to_bytes_per_channel(viewer->GetAudioLength())); - } - - return d; -} - -AudioPlaybackCache::Segment::Segment(qint64 size) -{ - size_ = size; -} - -AudioPlaybackCache::PlaybackDevice::PlaybackDevice(const AudioPlaybackCache::Playlist &playlist, int sample_sz, QObject *parent) : - QIODevice(parent), - playlist_(playlist), - current_segment_(0), - segment_read_index_(0), - sample_size_(sample_sz), - limit_(INT64_MAX) -{ -} - -AudioPlaybackCache::PlaybackDevice::~PlaybackDevice() -{ - close(); -} - -bool AudioPlaybackCache::PlaybackDevice::seek(qint64 pos) -{ - // Default behavior - QIODevice::seek(pos); - - // Find which segment we're in - current_segment_ = playlist_.GetIndexOfPosition(pos); - - // Catch failure to find index - if (current_segment_ == -1) { - return false; - } - - // Find position in segment - segment_read_index_ = pos - playlist_.at(current_segment_).offset(); - - return true; -} - -qint64 AudioPlaybackCache::PlaybackDevice::readData(char *data, qint64 maxSize) -{ - qint64 read_size = 0; - - while (read_size < maxSize - && current_segment_ >= 0 - && current_segment_ < playlist_.size() - && playlist_.at(current_segment_).offset() + segment_read_index_ < limit_) { - const Segment& cs = playlist_.at(current_segment_); - qint64 current_segment_sz = cs.size(); - - if (cs.offset() + current_segment_sz > limit_) { - current_segment_sz = limit_ - cs.offset(); + if (write_len > max_buffer_len) { + zero_len = write_len - max_buffer_len; + write_len = max_buffer_len; } - QVector segment_files(cs.channels()); - segment_files.fill(nullptr); + for (int channel=0; channelopen(QFile::ReadOnly)) { - // Seek to our stored index of this segment - f->seek(segment_read_index_); - } else { - all_files_opened = false; + if (!FileFunctions::DirectoryIsValid(QFileInfo(filename).dir())) { + success = false; break; } - } - // If all file handles opened successfully, time to interleave and send them out - if (all_files_opened) { - // Determine how many bytes to read - qint64 this_read_length = qMin((current_segment_sz - segment_read_index_) * cs.channels(), maxSize - read_size); + QFile f(filename); + if (f.open(QFile::ReadWrite)) { + f.seek(offset_in_segment); + f.write(reinterpret_cast(samples.data(channel)) + current_buffer_offset, write_len); - qint64 target = read_size + this_read_length; - - while (read_size < target) { - for (int i=0; iread(data + read_size, sample_size_); - - // Add to the read size - read_size += sample_size_; + if (zero_len > 0) { + QByteArray b(zero_len, 0); + f.write(b.constData()); } - // Add to the read index - segment_read_index_ += sample_size_; - } - - // If we've reached the end of this segment, tick the counter over to the next segment - if (segment_read_index_ == current_segment_sz) { - // Jump to the next file - segment_read_index_ = 0; - current_segment_++; + f.close(); + } else { + success = false; } } - // Close and delete file handles - for (int i=0; iisOpen()) { - f->close(); - } - delete f; - } - } + current_cache_offset += write_len; + current_buffer_offset += write_len; } - if (read_size < maxSize) { - // Zero out remaining data - memset(data + read_size, 0, maxSize - read_size); - } - - //return read_size; - return maxSize; + return success; } -int AudioPlaybackCache::Playlist::GetIndexOfPosition(qint64 pos) +QString AudioPlaybackCache::GetSegmentFilename(qint64 segment_index, int channel) { - if (this->isEmpty() - || pos < 0 - || pos >= GetLength()) { - return -1; - } - - if (pos < this->first().size()) { - return 0; - } - - if (pos > this->last().offset()) { - return this->size() - 1; - } - - // Use a binary search to find the segment with the right offset - int low = 0; - int high = this->size() - 1; - while (low <= high) { - int mid = low + (high - low) / 2; - - const Segment& mid_segment = this->at(mid); - if (mid_segment.offset() <= pos && mid_segment.offset() + mid_segment.size() > pos) { - return mid; - } else if (mid_segment.offset() < pos) { - low = mid + 1; - } else { - high = mid - 1; - } - } - - return -1; -} - -qint64 AudioPlaybackCache::Playlist::GetLength() const -{ - if (this->isEmpty()) { - return 0; - } - return this->last().offset() + this->last().size(); + return GetThisCacheDirectory().filePath(QStringLiteral("%1.%2").arg(QString::number(segment_index), QString::number(channel))); } } diff --git a/app/render/audioplaybackcache.h b/app/render/audioplaybackcache.h index 984135913..94f556037 100644 --- a/app/render/audioplaybackcache.h +++ b/app/render/audioplaybackcache.h @@ -72,136 +72,6 @@ public: void WriteSilence(const TimeRange &range); - void TrimIn(const rational &in); - - class Segment - { - public: - Segment(qint64 size = 0); - - qint64 size() const - { - return size_; - } - - void set_size(qint64 sz) - { - size_ = sz; - } - - qint64 offset() const - { - return offset_; - } - - void set_offset(qint64 o) - { - offset_ = o; - } - - int channels() const - { - return filenames_.size(); - } - - void set_channels(int index) - { - filenames_.resize(index); - } - - const QString& filename(int index) const - { - return filenames_.at(index); - } - - void set_filename(int index, const QString& filename) - { - filenames_[index] = filename; - } - - qint64 end() const - { - return offset_ + size_; - } - - private: - QVector filenames_; - - qint64 size_; - - qint64 offset_; - - }; - - class Playlist : public QVector - { - public: - Playlist() = default; - - int GetIndexOfPosition(qint64 pos); - - qint64 GetLength() const; - - }; - - class PlaybackDevice : public QIODevice - { - public: - PlaybackDevice(const Playlist& playlist, int sample_sz, QObject* parent = nullptr); - - void SetDataLimit(qint64 limit) - { - limit_ = limit; - } - - virtual ~PlaybackDevice() override; - - virtual bool isSequential() const override - { - return false; - } - - virtual bool seek(qint64 pos) override; - - virtual qint64 size() const override - { - return playlist_.GetLength(); - } - - virtual qint64 readData(char *data, qint64 maxSize) override; - - virtual qint64 writeData(const char *data, qint64 maxSize) override - { - Q_UNUSED(data) - Q_UNUSED(maxSize) - - return -1; - } - - private: - Playlist playlist_; - - int current_segment_; - - qint64 segment_read_index_; - - int sample_size_; - - qint64 limit_; - - }; - - /** - * @brief Create a QIODevice that can play whatever's in the cache currently - * - * This device will act very much like a QFile, transparently linking together various segments - * into what will appear to be a single contiguous file. - * - * The caller becomes responsible for ownership of the device, though the parent can be set - * automatically as an optional parameter to this function. - */ - PlaybackDevice* CreatePlaybackDevice(QObject *parent = nullptr) const; - const AudioVisualWaveform &visual() const { return visual_; } void set_visual(const AudioVisualWaveform &v) { visual_ = v; } @@ -211,26 +81,12 @@ signals: void WaveformUpdated(); private: + bool WritePartOfSampleBuffer(const SampleBuffer &samples, const rational &write_start, const rational &buffer_start, const rational &length); + + QString GetSegmentFilename(qint64 segment_index, int channel); + static const qint64 kDefaultSegmentSizePerChannel; - Segment CloneSegment(const Segment& s) const; - - Segment CreateSegment(const qint64 &size, const qint64 &offset) const; - - QString GenerateSegmentFilename() const; - - void TrimSegmentIn(Segment* s, qint64 new_length); - - void TrimSegmentOut(Segment* s, qint64 new_length); - - void RemoveSegmentFromArray(int index); - - void ClearPlaylist(); - - void UpdateOffsetsFrom(int index); - - Playlist playlist_; - AudioParams params_; AudioVisualWaveform visual_; diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index 44345f74d..a99d61a7a 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -239,7 +239,7 @@ QString FrameHashCache::CachePathName(const rational &time) const QString FrameHashCache::CachePathName(const QString &cache_path, const QUuid &cache_id, const int64_t &time) { - QString filename = QDir(QDir(cache_path).filePath(cache_id.toString())).filePath(QString::number(time)); + QString filename = GetThisCacheDirectory(cache_path, cache_id).filePath(QString::number(time)); // Register that in some way this hash has been accessed if (DiskManager::instance()) { diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 2fd2e046f..9c6e476b9 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -50,6 +50,16 @@ Node *PlaybackCache::parent() const return dynamic_cast(QObject::parent()); } +QDir PlaybackCache::GetThisCacheDirectory() const +{ + return GetThisCacheDirectory(GetCacheDirectory(), GetUuid()); +} + +QDir PlaybackCache::GetThisCacheDirectory(const QString &cache_path, const QUuid &cache_id) +{ + return QDir(cache_path).filePath(cache_id.toString()); +} + void PlaybackCache::InvalidateAll() { Invalidate(TimeRange(0, RATIONAL_MAX)); diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index 83c4365da..c3b464305 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -21,6 +21,7 @@ #ifndef PLAYBACKCACHE_H #define PLAYBACKCACHE_H +#include #include #include @@ -65,6 +66,9 @@ public: Node *parent() const; + QDir GetThisCacheDirectory() const; + static QDir GetThisCacheDirectory(const QString &cache_path, const QUuid &cache_id); + public slots: void InvalidateAll(); diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 107c32acf..d52c76dad 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -118,12 +118,11 @@ void PreviewAutoCacher::AudioRendered() SampleBuffer buf = watcher->Get().value(); node->audio_playback_cache()->SetParameters(buf.audio_params()); - /*if (node->audio_playback_cache()->IsEnabled()) { - // WritePCM is tolerant to its buffer being null, it will just write silence instead - node->audio_playback_cache()->WritePCM(range, - valid_ranges, - watcher->Get().value()); - }*/ + + // WritePCM is tolerant to its buffer being null, it will just write silence instead + node->audio_playback_cache()->WritePCM(range, + valid_ranges, + watcher->Get().value()); // Detect if this audio was incomplete because it was waiting on a conform to finish if (watcher->GetTicket()->property("incomplete").toBool()) { From 60f7f06de4eb07cde5d00fbf34440ab2f8078ae9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 25 May 2022 11:18:08 -0700 Subject: [PATCH 06/53] implemented base for thumbnail display --- app/node/block/clip/clip.cpp | 6 +- app/node/block/clip/clip.h | 9 + app/node/output/viewer/viewer.cpp | 9 - app/node/traverser.cpp | 17 +- app/render/framehashcache.cpp | 164 ++++++++---- app/render/framehashcache.h | 5 +- app/render/playbackcache.cpp | 2 +- app/render/playbackcache.h | 7 +- app/render/previewautocacher.cpp | 250 ++++++++++-------- app/render/previewautocacher.h | 54 ++-- app/render/rendermanager.cpp | 74 ++---- app/render/rendermanager.h | 73 ++++- app/render/renderprocessor.cpp | 44 +-- app/render/videoparams.cpp | 15 ++ app/render/videoparams.h | 2 + app/task/render/render.cpp | 38 +-- app/widget/timelinewidget/timelinewidget.cpp | 12 + app/widget/timelinewidget/timelinewidget.h | 2 + .../timelinewidget/view/timelineview.cpp | 29 +- app/widget/timelinewidget/view/timelineview.h | 11 + app/widget/viewer/viewer.cpp | 8 +- 21 files changed, 503 insertions(+), 328 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index b603d4af7..696f900ae 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -187,9 +187,9 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int if (Node *connected = GetConnectedOutput(from, element)) { TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, length())); if (type == Track::kVideo) { - emit connected->video_frame_cache()->Request(range.Intersected(max_range), true); + emit connected->video_frame_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); } else if (type == Track::kAudio) { - emit connected->audio_playback_cache()->Request(range.Intersected(max_range), true); + emit connected->audio_playback_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); } } } @@ -250,6 +250,7 @@ void ClipBlock::InputConnectedEvent(const QString &input, int element, Node *out super::InputConnectedEvent(input, element, output); if (input == kBufferIn) { + connect(output->video_frame_cache(), &FrameHashCache::ThumbnailsUpdated, this, &Block::PreviewChanged); connect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); } } @@ -259,6 +260,7 @@ void ClipBlock::InputDisconnectedEvent(const QString &input, int element, Node * super::InputDisconnectedEvent(input, element, output); if (input == kBufferIn) { + disconnect(output->video_frame_cache(), &FrameHashCache::ThumbnailsUpdated, this, &Block::PreviewChanged); disconnect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index dd075b728..dab1ecf41 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -119,6 +119,15 @@ public: return block_links_; } + const FrameHashCache *thumbnails() + { + if (Node *n = GetConnectedOutput(kBufferIn)) { + return n->video_frame_cache(); + } else { + return nullptr; + } + } + const AudioVisualWaveform *waveform() { if (Node *n = GetConnectedOutput(kBufferIn)) { diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 541698d55..e40022ff5 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -389,10 +389,6 @@ void ViewerOutput::InputValueChangedEvent(const QString &input, int element) } if (frame_rate_changed) { - // FIXME: Will need to find a better way to update this soon - //if (video_frame_cache()->IsEnabled()) { - video_frame_cache()->SetTimebase(new_video_params.frame_rate_as_time_base()); - //} emit FrameRateChanged(new_video_params.frame_rate()); } @@ -412,11 +408,6 @@ void ViewerOutput::InputValueChangedEvent(const QString &input, int element) emit AudioParamsChanged(); - // FIXME: Will need to find a better way to update this soon - //if (audio_playback_cache()->IsEnabled()) { - audio_playback_cache()->SetParameters(GetAudioParams()); - //} - cached_audio_params_ = new_audio_params; } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index a75ab921e..da04f5e99 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -443,17 +443,14 @@ void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) VideoParams render_params = GetCacheVideoParams(); VideoParams job_params = job.video_params(); - // HACK/FIXME: Override old cached probe data that contains an invalid divider. Might be - // good in the future to version the probe data so we can automatically - // ignore older stuff. - job_params.set_divider(render_params.divider()); - - // See if we can make this divider larger (i.e. if the footage is smaller) - while (job_params.divider() > 1 - && VideoParams::GetScaledDimension(job_params.width(), job_params.divider()-1) < render_params.effective_width() - && VideoParams::GetScaledDimension(job_params.height(), job_params.divider()-1) < render_params.effective_height()) { - job_params.set_divider(job_params.divider() - 1); + if (render_params.divider() > 1) { + // Use a divider appropriate for this target resolution + job_params.set_divider(VideoParams::GetDividerForTargetResolution(job_params.width(), job_params.height(), render_params.effective_width(), render_params.effective_height())); + } else { + // Render everything at full res + job_params.set_divider(1); } + job.set_video_params(job_params); if (footage_time.isNaN()) { diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index a99d61a7a..ad0832459 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -32,12 +32,11 @@ #include "codec/frame.h" #include "common/filefunctions.h" +#include "common/oiioutils.h" #include "render/diskmanager.h" namespace olive { -const QString FrameHashCache::kCacheFormatExtension = QStringLiteral(".exr"); - #define super PlaybackCache FrameHashCache::FrameHashCache(QObject *parent) : @@ -256,63 +255,132 @@ QString FrameHashCache::CachePathName(const QString &cache_path, const QUuid &ca bool FrameHashCache::SaveCacheFrame(const QString &filename, const FramePtr frame) { - if (!VideoParams::FormatIsFloat(frame->format())) { - return false; - } - // Ensure directory is created QDir cache_dir = QFileInfo(filename).dir(); if (!FileFunctions::DirectoryIsValid(cache_dir)) { return false; } - // Floating point types are stored in EXR - Imf::PixelType pix_type; + if (VideoParams::FormatIsFloat(frame->format())) { + // Floating point types are stored in EXR + Imf::PixelType pix_type; - if (frame->format() == VideoParams::kFormatFloat16) { - pix_type = Imf::HALF; - } else { - pix_type = Imf::FLOAT; - } - - Imf::Header header(frame->width(), frame->height()); - header.channels().insert("R", Imf::Channel(pix_type)); - header.channels().insert("G", Imf::Channel(pix_type)); - header.channels().insert("B", Imf::Channel(pix_type)); - if (frame->channel_count() == VideoParams::kRGBAChannelCount) { - header.channels().insert("A", Imf::Channel(pix_type)); - } - - header.compression() = Imf::DWAA_COMPRESSION; - header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f)); - header.pixelAspectRatio() = frame->video_params().pixel_aspect_ratio().toDouble(); - - header.insert("oliveDivider", Imf::IntAttribute(frame->video_params().divider())); - - try { - Imf::OutputFile out(filename.toUtf8(), header, 0); - - int bpc = VideoParams::GetBytesPerChannel(frame->format()); - - size_t xs = frame->channel_count() * bpc; - size_t ys = frame->linesize_bytes(); - - Imf::FrameBuffer framebuffer; - framebuffer.insert("R", Imf::Slice(pix_type, frame->data(), xs, ys)); - framebuffer.insert("G", Imf::Slice(pix_type, frame->data() + bpc, xs, ys)); - framebuffer.insert("B", Imf::Slice(pix_type, frame->data() + 2*bpc, xs, ys)); - if (frame->channel_count() == VideoParams::kRGBAChannelCount) { - framebuffer.insert("A", Imf::Slice(pix_type, frame->data() + 3*bpc, xs, ys)); + if (frame->format() == VideoParams::kFormatFloat16) { + pix_type = Imf::HALF; + } else { + pix_type = Imf::FLOAT; } - out.setFrameBuffer(framebuffer); - out.writePixels(frame->height()); + Imf::Header header(frame->width(), frame->height()); + header.channels().insert("R", Imf::Channel(pix_type)); + header.channels().insert("G", Imf::Channel(pix_type)); + header.channels().insert("B", Imf::Channel(pix_type)); + if (frame->channel_count() == VideoParams::kRGBAChannelCount) { + header.channels().insert("A", Imf::Channel(pix_type)); + } - return true; - } catch (const std::exception &e) { - qCritical() << "Failed to write cache frame:" << e.what(); + header.compression() = Imf::DWAA_COMPRESSION; + header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f)); + header.pixelAspectRatio() = frame->video_params().pixel_aspect_ratio().toDouble(); - return false; + header.insert("oliveDivider", Imf::IntAttribute(frame->video_params().divider())); + + try { + Imf::OutputFile out(filename.toUtf8(), header, 0); + + int bpc = VideoParams::GetBytesPerChannel(frame->format()); + + size_t xs = frame->channel_count() * bpc; + size_t ys = frame->linesize_bytes(); + + Imf::FrameBuffer framebuffer; + framebuffer.insert("R", Imf::Slice(pix_type, frame->data(), xs, ys)); + framebuffer.insert("G", Imf::Slice(pix_type, frame->data() + bpc, xs, ys)); + framebuffer.insert("B", Imf::Slice(pix_type, frame->data() + 2*bpc, xs, ys)); + if (frame->channel_count() == VideoParams::kRGBAChannelCount) { + framebuffer.insert("A", Imf::Slice(pix_type, frame->data() + 3*bpc, xs, ys)); + } + out.setFrameBuffer(framebuffer); + + out.writePixels(frame->height()); + + return true; + } catch (const std::exception &e) { + qCritical() << "Failed to write cache frame:" << e.what(); + + return false; + } + } else { + QImage::Format fmt = QImage::Format_Invalid; + + switch (frame->format()) { + case VideoParams::kFormatUnsigned8: + if (frame->channel_count() == VideoParams::kRGBAChannelCount){ + fmt = QImage::Format_RGBA8888_Premultiplied; + } else if (frame->channel_count() == VideoParams::kRGBChannelCount){ + fmt = QImage::Format_RGB888; + } + break; + case VideoParams::kFormatUnsigned16: + if (frame->channel_count() == VideoParams::kRGBAChannelCount){ + fmt = QImage::Format_RGBA64_Premultiplied; + } + break; + case VideoParams::kFormatFloat16: + case VideoParams::kFormatFloat32: + case VideoParams::kFormatCount: + case VideoParams::kFormatInvalid: + break; + } + + if (fmt == QImage::Format_Invalid) { + return false; + } + + QImage img(reinterpret_cast(frame->data()), frame->width(), frame->height(), frame->linesize_bytes(), fmt); + + return img.save(filename, "jpg"); + + + /* + qDebug() << "hello?" << filename; + + // Integer types are stored in JPG + QString tmp = filename; + tmp.append(QStringLiteral(".jpg")); + + std::string tmp_std = tmp.toStdString(); + auto out = OIIO::ImageOutput::create(tmp_std); + if (!out) { + qDebug() << "fail create"; + return false; + } + + auto fmt = OIIOUtils::GetOIIOBaseTypeFromFormat(frame->format()); + qDebug() << "writing" << fmt; + if (!out->open(tmp_std, OIIO::ImageSpec(frame->width(), frame->height(), frame->channel_count(), fmt))) { + qDebug() << "fail open"; + return false; + } + + bool ret = out->write_image(fmt, frame->data(), OIIO::AutoStride, frame->linesize_bytes()); + out->close(); + + if (ret) { + QFile f(filename); + if (f.exists()) { + f.remove(); + } + ret = QFile::rename(tmp, filename); + if (!ret) { + qDebug() << "fail rename from" << tmp << "to" << filename; + } + } else { + qDebug() << "fail write"; + } + + return ret; + */ } } diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index a98432ac5..d45228a7c 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -65,6 +65,9 @@ public: FramePtr LoadCacheFrame(const int64_t &time) const; static FramePtr LoadCacheFrame(const QString& fn); +signals: + void ThumbnailsUpdated(); + private: rational ToTime(const int64_t &ts) const; int64_t ToTimestamp(const rational &ts, Timecode::Rounding rounding = Timecode::kRound) const; @@ -80,8 +83,6 @@ private: rational timebase_; - static const QString kCacheFormatExtension; - private slots: void HashDeleted(const QString &path, const QString &filename); diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 9c6e476b9..762393223 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -41,7 +41,7 @@ void PlaybackCache::Invalidate(const TimeRange &r) emit Invalidated(r); if (automatic_) { - emit Request(r, false); + emit Request(r, kCacheOnly); } } diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index c3b464305..64045257b 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -69,6 +69,11 @@ public: QDir GetThisCacheDirectory() const; static QDir GetThisCacheDirectory(const QString &cache_path, const QUuid &cache_id); + enum RequestType { + kCacheOnly, + kPreviewsOnly + }; + public slots: void InvalidateAll(); @@ -77,7 +82,7 @@ signals: void Validated(const olive::TimeRange& r); - void Request(const olive::TimeRange& r, bool previews_only); + void Request(const olive::TimeRange& r, olive::PlaybackCache::RequestType type); void AutomaticChanged(bool e); diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index d52c76dad..36d60b099 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -30,6 +30,7 @@ #include "task/customcache/customcachetask.h" #include "task/taskmanager.h" #include "widget/slider/base/numericsliderbase.h" +#include "widget/viewer/viewer.h" namespace olive { @@ -38,7 +39,8 @@ PreviewAutoCacher::PreviewAutoCacher(QObject *parent) : viewer_node_(nullptr), use_custom_range_(false), pause_renders_(false), - single_frame_render_(nullptr) + single_frame_render_(nullptr), + display_color_processor_(nullptr) { // Set defaults SetPlayhead(0); @@ -78,21 +80,21 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, RenderTicke RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, RenderTicketPriority priority) { - return RenderAudio(range, false, priority); + return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, PlaybackCache::kCacheOnly, priority); } -void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range) +void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range, PlaybackCache::RequestType type) { FrameHashCache *cache = static_cast(sender()); - VideoInvalidatedFromNode(cache->parent(), range); + VideoInvalidatedFromNode(cache->parent(), range, type); } -void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range) +void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range, PlaybackCache::RequestType type) { AudioPlaybackCache *cache = static_cast(sender()); - AudioInvalidatedFromNode(cache->parent(), range); + AudioInvalidatedFromNode(cache->parent(), range, type); } void PreviewAutoCacher::AudioRendered() @@ -105,9 +107,9 @@ void PreviewAutoCacher::AudioRendered() if (audio_tasks_.contains(watcher)) { // Assume that a "result" is a fully completed image and a non-result is a cancelled ticket TimeRange range = audio_tasks_.take(watcher); - Node *node = Node::ValueToPtr(watcher->property("node")); + Node *node = copy_map_.key(Node::ValueToPtr(watcher->property("node"))); - if (watcher->HasResult()) { + if (watcher->HasResult() && node) { AudioCacheData &d = audio_cache_data_[node]; JobTime watcher_job_time = watcher->property("job").value(); @@ -119,23 +121,26 @@ void PreviewAutoCacher::AudioRendered() SampleBuffer buf = watcher->Get().value(); node->audio_playback_cache()->SetParameters(buf.audio_params()); - // WritePCM is tolerant to its buffer being null, it will just write silence instead - node->audio_playback_cache()->WritePCM(range, - valid_ranges, - watcher->Get().value()); + PlaybackCache::RequestType type = PlaybackCache::RequestType(watcher->property("type").toInt()); - // Detect if this audio was incomplete because it was waiting on a conform to finish - if (watcher->GetTicket()->property("incomplete").toBool()) { - if (last_conform_task_ > watcher_job_time) { - // Requeue now - node->audio_playback_cache()->Invalidate(range); - } else { - // Wait for conform - d.needing_conform.insert(range); - } + if (type == PlaybackCache::kCacheOnly) { + // WritePCM is tolerant to its buffer being null, it will just write silence instead + node->audio_playback_cache()->WritePCM(range, + valid_ranges, + watcher->Get().value()); } else { - qDebug() << "Writing waveforms to" << range << valid_ranges; - node->audio_playback_cache()->WriteWaveform(range, valid_ranges, &waveform); + // Detect if this audio was incomplete because it was waiting on a conform to finish + if (watcher->GetTicket()->property("incomplete").toBool()) { + if (last_conform_task_ > watcher_job_time) { + // Requeue now + node->audio_playback_cache()->Invalidate(range); + } else { + // Wait for conform + d.needing_conform.insert(range); + } + } else { + node->audio_playback_cache()->WriteWaveform(range, valid_ranges, &waveform); + } } } @@ -157,10 +162,17 @@ void PreviewAutoCacher::VideoRendered() if (it != video_tasks_.end()) { // Assume that a "result" is a fully completed image and a non-result is a cancelled ticket if (watcher->HasResult()) { - // Download frame in another thread - if (watcher->GetTicket()->property("cached").toBool()) { + PlaybackCache::RequestType type = PlaybackCache::RequestType(watcher->property("type").toInt()); + + if (type == PlaybackCache::kCacheOnly) { + if (watcher->GetTicket()->property("cached").toBool()) { + if (FrameHashCache *cache = Node::ValueToPtr(watcher->property("cache"))) { + cache->ValidateTime(it.value()); + } + } + } else { if (FrameHashCache *cache = Node::ValueToPtr(watcher->property("cache"))) { - cache->ValidateTime(it.value()); + emit cache->ThumbnailsUpdated(); } } } @@ -392,14 +404,14 @@ void PreviewAutoCacher::CancelQueuedSingleFrameRender() void PreviewAutoCacher::VideoInvalidatedList(Node *node, const TimeRangeList &list) { foreach (const TimeRange &range, list) { - VideoInvalidatedFromNode(node, range); + VideoInvalidatedFromNode(node, range, PlaybackCache::kCacheOnly); } } void PreviewAutoCacher::AudioInvalidatedList(Node *node, const TimeRangeList &list) { foreach (const TimeRange &range, list) { - AudioInvalidatedFromNode(node, range); + AudioInvalidatedFromNode(node, range, PlaybackCache::kCacheOnly); } } @@ -409,42 +421,40 @@ void PreviewAutoCacher::StartCachingRange(const TimeRange &range, TimeRangeList tracker->insert(range, graph_changed_time_); } -void PreviewAutoCacher::StartCachingVideoRange(Node *node, const TimeRange &range) +void PreviewAutoCacher::StartCachingVideoRange(Node *node, const TimeRange &range, PlaybackCache::RequestType type) { - VideoCacheData &d = video_cache_data_[node]; - - StartCachingRange(range, &d.invalidated, &d.job_tracker); + pending_video_jobs_.push_back({node, range, TimeRangeListFrameIterator({range}, viewer_node_->GetVideoParams().frame_rate_as_time_base()), type}); + video_cache_data_[node].job_tracker.insert(range, graph_changed_time_); TryRender(); } -void PreviewAutoCacher::StartCachingAudioRange(Node *node, const TimeRange &range) +void PreviewAutoCacher::StartCachingAudioRange(Node *node, const TimeRange &range, PlaybackCache::RequestType type) { - AudioCacheData &d = audio_cache_data_[node]; - - StartCachingRange(range, &d.invalidated, &d.job_tracker); + pending_audio_jobs_.push_back({node, range, type}); + audio_cache_data_[node].job_tracker.insert(range, graph_changed_time_); TryRender(); } -void PreviewAutoCacher::VideoInvalidatedFromNode(Node *node, const TimeRange &range) +void PreviewAutoCacher::VideoInvalidatedFromNode(Node *node, const TimeRange &range, PlaybackCache::RequestType type) { // Stop any current render tasks because a) they might be out of date now anyway, and b) we // want to dedicate all our rendering power to realtime feedback for the user - CancelVideoTasks(node); + //CancelVideoTasks(node); // If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames if (!NodeInputDragger::IsInputBeingDragged()) { - StartCachingVideoRange(node, range); + StartCachingVideoRange(node, range, type); } } -void PreviewAutoCacher::AudioInvalidatedFromNode(Node *node, const TimeRange &range) +void PreviewAutoCacher::AudioInvalidatedFromNode(Node *node, const TimeRange &range, PlaybackCache::RequestType type) { // We don't stop rendering audio because currently there's no system of requeuing audio if it's // cancelled, so some areas may end up unrendered forever // ClearAudioQueue(); // If we're auto-caching audio or require realtime waveforms, we'll have to render this - StartCachingAudioRange(node, range); + StartCachingAudioRange(node, range, type); } void PreviewAutoCacher::VideoAutoCacheEnableChangedFromNode(Node *node, bool e) @@ -501,8 +511,9 @@ void PreviewAutoCacher::CancelAudioTasks(bool and_wait_for_them_to_finish) bool PreviewAutoCacher::IsRenderingCustomRange() const { - const VideoCacheData &d = video_cache_data_.value(viewer_node_); - return d.iterator.IsCustomRange() && d.iterator.HasNext(); + /*const VideoCacheData &d = video_cache_data_.value(viewer_node_); + return d.iterator.IsCustomRange() && d.iterator.HasNext();*/ + return false; } void PreviewAutoCacher::SetRendersPaused(bool e) @@ -553,10 +564,6 @@ void PreviewAutoCacher::TryRender() { delayed_requeue_timer_.stop(); - if (pause_renders_) { - return; - } - if (!graph_update_queue_.isEmpty()) { // 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 @@ -572,7 +579,9 @@ void PreviewAutoCacher::TryRender() if (single_frame_render_) { // Check if already caching this - RenderTicketWatcher *watcher = RenderFrame(single_frame_render_->property("time").value(), + RenderTicketWatcher *watcher = RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), + single_frame_render_->property("time").value(), + PlaybackCache::kCacheOnly, RenderTicketPriority(single_frame_render_->property("priority").toInt()), nullptr); video_immediate_passthroughs_[watcher].append(single_frame_render_); @@ -580,102 +589,111 @@ void PreviewAutoCacher::TryRender() single_frame_render_ = nullptr; } - // Ensure we are running tasks if we have any - const int max_tasks = RenderManager::GetNumberOfIdealConcurrentJobs(); + if (!pause_renders_) { + // Ensure we are running tasks if we have any + const int max_tasks = RenderManager::GetNumberOfIdealConcurrentJobs(); - // Handle video tasks - for (auto it=video_cache_data_.begin(); it!=video_cache_data_.end(); it++) { - VideoCacheData &d = it.value(); + // Handle video tasks + while (!pending_video_jobs_.empty()) { + VideoJob &d = pending_video_jobs_.front(); - // Check for newly invalidated video - if (!d.invalidated.isEmpty()) { - if (d.iterator.HasNext()) { - d.iterator.insert(d.invalidated); + if (Node *copy = copy_map_.value(d.node)) { + // Queue next frames + rational t; + while (video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { + RenderTicketWatcher* render_task = video_tasks_.key(t); + + // We want this hash, if we're not already rendering, start render now + if (!render_task) { + // Don't render any hash more than once + RenderFrame(copy, t, d.type, RenderTicketPriority::kNormal, d.node->video_frame_cache()); + } + + emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); + + if (!d.iterator.HasNext()) { + emit StopCacheProxyTasks(); + } + } } else { - d.iterator = TimeRangeListFrameIterator(d.invalidated, viewer_node_->GetVideoParams().frame_rate_as_time_base()); - } - d.invalidated.clear(); - } - - // Queue next frames - rational t; - while (video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { - RenderTicketWatcher* render_task = video_tasks_.key(t); - - // We want this hash, if we're not already rendering, start render now - if (!render_task) { - // Don't render any hash more than once - RenderFrame(it.key(), t, RenderTicketPriority::kNormal, it.key()->video_frame_cache()); + qCritical() << "Failed to find node copy for video job"; } - emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); - - if (!d.iterator.HasNext()) { - emit StopCacheProxyTasks(); + if (d.iterator.HasNext()) { + break; + } else { + pending_video_jobs_.pop_front(); } } - } - - // Handle audio tasks - for (auto it=audio_cache_data_.begin(); it!=audio_cache_data_.end(); it++) { - AudioCacheData &d = it.value(); - - if (!d.invalidated.isEmpty()) { - // Add newly invalidated audio to iterator - d.iterator.insert(d.invalidated); - d.invalidated.clear(); - } - - while (!d.iterator.isEmpty() && audio_tasks_.size() < max_tasks) { - // Copy first range in list - TimeRange r = d.iterator.first(); - - // Limit to the minimum sample rate supported by AudioVisualWaveform - we use this value so that - // whatever chunk we render can be summed down to the smallest mipmap whole - r.set_out(qMin(r.out(), r.in() + AudioVisualWaveform::kMinimumSampleRate.flipped())); + // Handle audio tasks + while (!pending_audio_jobs_.empty()) { + AudioJob &d = pending_audio_jobs_.front(); // Start job - RenderAudio(it.key(), r, true, RenderTicketPriority::kNormal); + if (Node *copy = copy_map_.value(d.node)) { + RenderAudio(copy, d.range, d.type, RenderTicketPriority::kNormal); + } else { + qCritical() << "Failed to find node copy for audio job"; + } - d.iterator.remove(r); + pending_audio_jobs_.pop_front(); } } } -RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, RenderTicketPriority priority, FrameHashCache *cache) +RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, PlaybackCache::RequestType type, RenderTicketPriority priority, FrameHashCache *cache) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); watcher->setProperty("cache", Node::PtrToValue(cache)); - if (cache) { - cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); - } + watcher->setProperty("type", type); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered); video_tasks_.insert(watcher, time); - watcher->SetTicket(RenderManager::instance()->RenderFrame(node, - copied_viewer_node_->GetVideoParams(), - copied_viewer_node_->GetAudioParams(), - copied_color_manager_, - time, - RenderMode::kOffline, - cache, - priority, - RenderManager::kTexture)); + + RenderManager::RenderVideoParams rvp(node, + copied_viewer_node_->GetVideoParams(), + copied_viewer_node_->GetAudioParams(), + time, + copied_color_manager_); + + if (cache) { + cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); + rvp.AddCache(cache); + + if (type == PlaybackCache::kPreviewsOnly) { + rvp.video_params.set_divider(VideoParams::GetDividerForTargetResolution(rvp.video_params.width(), rvp.video_params.height(), 160, 120)); + rvp.force_color_output = display_color_processor_; + rvp.force_format = VideoParams::kFormatUnsigned8; + } + } + + rvp.priority = priority; + rvp.return_type = RenderManager::kTexture; + rvp.use_cache = true; + + watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp)); + return watcher; } -RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, bool generate_waveforms, RenderTicketPriority priority) +RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, PlaybackCache::RequestType type, RenderTicketPriority priority) { - qDebug() << "Rendering" << r << "for" << node; - RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); watcher->setProperty("node", Node::PtrToValue(node)); + watcher->setProperty("type", type); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); audio_tasks_.insert(watcher, r); - RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(node, r, copied_viewer_node_->GetAudioParams(), RenderMode::kOffline, generate_waveforms, priority); + RenderManager::RenderAudioParams rap(node, + r, + copied_viewer_node_->GetAudioParams()); + + rap.generate_waveforms = (type == PlaybackCache::kPreviewsOnly); + rap.priority = priority; + + RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(rap); watcher->SetTicket(ticket); return ticket; } @@ -714,9 +732,7 @@ void PreviewAutoCacher::AudioAutoCacheEnableChanged(bool e) void PreviewAutoCacher::CacheProxyTaskCancelled() { - for (auto it=video_cache_data_.begin(); it!=video_cache_data_.end(); it++) { - it->iterator.reset(); - } + pending_video_jobs_.clear(); TryRender(); } @@ -727,7 +743,7 @@ void PreviewAutoCacher::ForceCacheRange(const TimeRange &range) custom_autocache_range_ = range; // Re-hash these frames and start rendering - StartCachingVideoRange(viewer_node_, range); + StartCachingVideoRange(viewer_node_, range, PlaybackCache::kCacheOnly); } void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 0572bbd31..933ac5033 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -89,6 +89,12 @@ public: void SetRendersPaused(bool e); +public slots: + void SetDisplayColorProcessor(ColorProcessorPtr processor) + { + display_color_processor_ = processor; + } + signals: void StopCacheProxyTasks(); @@ -97,17 +103,9 @@ signals: private: void TryRender(); - RenderTicketWatcher *RenderFrame(Node *node, const rational &time, RenderTicketPriority priority, FrameHashCache *cache); - RenderTicketWatcher *RenderFrame(const rational &time, RenderTicketPriority priority, FrameHashCache *cache) - { - return RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), time, priority, cache); - } + RenderTicketWatcher *RenderFrame(Node *node, const rational &time, PlaybackCache::RequestType type, RenderTicketPriority priority, FrameHashCache *cache); - RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, bool generate_waveforms, RenderTicketPriority priority); - RenderTicketPtr RenderAudio(const TimeRange &range, bool generate_waveforms, RenderTicketPriority priority) - { - return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, generate_waveforms, priority); - } + RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, PlaybackCache::RequestType type, RenderTicketPriority priority); /** * @brief Process all changes to internal NodeGraph copy @@ -138,11 +136,11 @@ private: void AudioInvalidatedList(Node *node, const TimeRangeList &list); void StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker); - void StartCachingVideoRange(Node *node, const TimeRange &range); - void StartCachingAudioRange(Node *node, const TimeRange &range); + void StartCachingVideoRange(Node *node, const TimeRange &range, PlaybackCache::RequestType type); + void StartCachingAudioRange(Node *node, const TimeRange &range, PlaybackCache::RequestType type); - void VideoInvalidatedFromNode(Node *node, const olive::TimeRange &range); - void AudioInvalidatedFromNode(Node *node, const olive::TimeRange &range); + void VideoInvalidatedFromNode(Node *node, const olive::TimeRange &range, PlaybackCache::RequestType type); + void AudioInvalidatedFromNode(Node *node, const olive::TimeRange &range, PlaybackCache::RequestType type); void VideoAutoCacheEnableChangedFromNode(Node *node, bool e); void AudioAutoCacheEnableChangedFromNode(Node *node, bool e); @@ -195,32 +193,46 @@ private: QMap audio_tasks_; QMap video_tasks_; - struct VideoCacheData { - TimeRangeList invalidated; - RenderJobTracker job_tracker; + struct VideoJob { + Node *node; + TimeRange range; TimeRangeListFrameIterator iterator; + PlaybackCache::RequestType type; + }; + + struct VideoCacheData { + RenderJobTracker job_tracker; + }; + + struct AudioJob { + Node *node; + TimeRange range; + PlaybackCache::RequestType type; }; struct AudioCacheData { - TimeRangeList invalidated; TimeRangeList needing_conform; RenderJobTracker job_tracker; - TimeRangeList iterator; }; + std::list pending_video_jobs_; + std::list pending_audio_jobs_; + QHash video_cache_data_; QHash audio_cache_data_; + ColorProcessorPtr display_color_processor_; + private slots: /** * @brief Handler for when the NodeGraph reports a video change over a certain time range */ - void VideoInvalidatedFromCache(const olive::TimeRange &range); + void VideoInvalidatedFromCache(const olive::TimeRange &range, olive::PlaybackCache::RequestType type); /** * @brief Handler for when the NodeGraph reports a audio change over a certain time range */ - void AudioInvalidatedFromCache(const olive::TimeRange &range); + void AudioInvalidatedFromCache(const olive::TimeRange &range, olive::PlaybackCache::RequestType type); /** * @brief Handler for when the RenderManager has returned rendered audio diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index c34b050d8..09d20df00 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -76,73 +76,45 @@ RenderManager::~RenderManager() } } -RenderTicketPtr RenderManager::RenderFrame(Node *node, const VideoParams &vparam, const AudioParams ¶m, - ColorManager* color_manager, const rational& time, RenderMode::Mode mode, - FrameHashCache* cache, RenderTicketPriority priority, ReturnType return_type) -{ - return RenderFrame(node, - color_manager, - time, - mode, - vparam, - param, - QSize(0, 0), - QMatrix4x4(), - VideoParams::kFormatInvalid, - nullptr, - cache, - priority, - return_type); -} - -RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manager, - const rational& time, RenderMode::Mode mode, - const VideoParams &video_params, const AudioParams &audio_params, - const QSize& force_size, - const QMatrix4x4& force_matrix, VideoParams::Format force_format, - ColorProcessorPtr force_color_output, - FrameHashCache* cache, RenderTicketPriority priority, ReturnType return_type) +RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams ¶ms) { // Create ticket RenderTicketPtr ticket = std::make_shared(); - ticket->setProperty("node", Node::PtrToValue(node)); - ticket->setProperty("time", QVariant::fromValue(time)); - ticket->setProperty("size", force_size); - ticket->setProperty("matrix", force_matrix); - ticket->setProperty("format", force_format); - ticket->setProperty("mode", mode); + ticket->setProperty("node", Node::PtrToValue(params.node)); + ticket->setProperty("time", QVariant::fromValue(params.time)); + ticket->setProperty("size", params.force_size); + ticket->setProperty("matrix", params.force_matrix); + ticket->setProperty("format", params.force_format); + ticket->setProperty("usecache", params.use_cache); ticket->setProperty("type", kTypeVideo); - ticket->setProperty("colormanager", Node::PtrToValue(color_manager)); - ticket->setProperty("coloroutput", QVariant::fromValue(force_color_output)); - ticket->setProperty("vparam", QVariant::fromValue(video_params)); - ticket->setProperty("aparam", QVariant::fromValue(audio_params)); - ticket->setProperty("return", return_type); + ticket->setProperty("colormanager", Node::PtrToValue(params.color_manager)); + ticket->setProperty("coloroutput", QVariant::fromValue(params.force_color_output)); + Q_ASSERT(params.video_params.is_valid()); + ticket->setProperty("vparam", QVariant::fromValue(params.video_params)); + ticket->setProperty("aparam", QVariant::fromValue(params.audio_params)); + ticket->setProperty("return", params.return_type); + ticket->setProperty("cache", params.cache_dir); + ticket->setProperty("cachetimebase", QVariant::fromValue(params.cache_timebase)); + ticket->setProperty("cacheid", QVariant::fromValue(params.cache_id)); - if (cache) { - ticket->setProperty("cache", cache->GetCacheDirectory()); - ticket->setProperty("cachetimebase", QVariant::fromValue(cache->GetTimebase())); - ticket->setProperty("cacheuuid", QVariant::fromValue(cache->GetUuid())); - } - - AddTicket(ticket, priority); + AddTicket(ticket, params.priority); return ticket; } -RenderTicketPtr RenderManager::RenderAudio(Node *node, const TimeRange &r, const AudioParams ¶ms, RenderMode::Mode mode, bool generate_waveforms, RenderTicketPriority priority) +RenderTicketPtr RenderManager::RenderAudio(const RenderAudioParams ¶ms) { // Create ticket RenderTicketPtr ticket = std::make_shared(); - ticket->setProperty("node", Node::PtrToValue(node)); - ticket->setProperty("time", QVariant::fromValue(r)); + ticket->setProperty("node", Node::PtrToValue(params.node)); + ticket->setProperty("time", QVariant::fromValue(params.range)); ticket->setProperty("type", kTypeAudio); - ticket->setProperty("mode", mode); - ticket->setProperty("enablewaveforms", generate_waveforms); - ticket->setProperty("aparam", QVariant::fromValue(params)); + ticket->setProperty("enablewaveforms", params.generate_waveforms); + ticket->setProperty("aparam", QVariant::fromValue(params.audio_params)); - AddTicket(ticket, priority); + AddTicket(ticket, params.priority); return ticket; } diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 8753f7c90..6ef2a503a 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -68,6 +68,49 @@ public: kFrame }; + struct RenderVideoParams { + RenderVideoParams(Node *n, const VideoParams &vparam, const AudioParams &aparam, const rational &t, + ColorManager *colorman) + { + node = n; + video_params = vparam; + audio_params = aparam; + time = t; + color_manager = colorman; + use_cache = false; + priority = RenderTicketPriority::kNormal; + return_type = kFrame; + force_format = VideoParams::kFormatInvalid; + force_color_output = nullptr; + force_size = QSize(0, 0); + } + + void AddCache(FrameHashCache *cache) + { + cache_dir = cache->GetCacheDirectory(); + cache_timebase = cache->GetTimebase(); + cache_id = cache->GetUuid().toString(); + } + + Node *node; + VideoParams video_params; + AudioParams audio_params; + rational time; + ColorManager *color_manager; + bool use_cache; + RenderTicketPriority priority; + ReturnType return_type; + + QString cache_dir; + rational cache_timebase; + QString cache_id; + + QSize force_size; + QMatrix4x4 force_matrix; + VideoParams::Format force_format; + ColorProcessorPtr force_color_output; + }; + /** * @brief Asynchronously generate a frame at a given time * @@ -76,16 +119,24 @@ public: * * This function is thread-safe. */ - RenderTicketPtr RenderFrame(Node *node, const VideoParams &vparam, const AudioParams ¶m, ColorManager* color_manager, - const rational& time, RenderMode::Mode mode, - FrameHashCache* cache = nullptr, RenderTicketPriority priority = RenderTicketPriority::kNormal, ReturnType return_type = kFrame); - RenderTicketPtr RenderFrame(Node *node, ColorManager* color_manager, - const rational& time, RenderMode::Mode mode, - const VideoParams& video_params, const AudioParams& audio_params, - const QSize& force_size, - const QMatrix4x4& force_matrix, VideoParams::Format force_format, - ColorProcessorPtr force_color_output, - FrameHashCache* cache = nullptr, RenderTicketPriority priority = RenderTicketPriority::kNormal, ReturnType return_type = kFrame); + RenderTicketPtr RenderFrame(const RenderVideoParams ¶ms); + + struct RenderAudioParams { + RenderAudioParams(Node *n, const TimeRange &time, const AudioParams &aparam) + { + node = n; + range = time; + audio_params = aparam; + generate_waveforms = false; + priority = RenderTicketPriority::kNormal; + } + + Node *node; + TimeRange range; + AudioParams audio_params; + bool generate_waveforms; + RenderTicketPriority priority; + }; /** * @brief Asynchronously generate a chunk of audio @@ -94,7 +145,7 @@ public: * * This function is thread-safe. */ - RenderTicketPtr RenderAudio(Node *viewer, const TimeRange& r, const AudioParams& params, RenderMode::Mode mode, bool generate_waveforms, RenderTicketPriority priority = RenderTicketPriority::kNormal); + RenderTicketPtr RenderAudio(const RenderAudioParams ¶ms); virtual void RunTicket(RenderTicketPtr ticket) const override; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 67e88843d..aa38a9745 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -91,32 +91,32 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time ColorProcessorPtr output_color_transform = ticket_->property("coloroutput").value(); const VideoParams& tex_params = texture->params(); + if (output_color_transform) { + TexturePtr transform_tex = render_ctx_->CreateTexture(tex_params); + ColorTransformJob job; + + job.SetColorProcessor(output_color_transform); + job.SetInputTexture(texture); + job.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone); + + render_ctx_->BlitColorManaged(job, transform_tex.get()); + + texture = transform_tex; + } + if (tex_params.effective_width() != frame_params.effective_width() || tex_params.effective_height() != frame_params.effective_height() - || tex_params.format() != frame_params.format() - || output_color_transform) { + || tex_params.format() != frame_params.format()) { TexturePtr blit_tex = render_ctx_->CreateTexture(frame_params); QMatrix4x4 matrix = ticket_->property("matrix").value(); - if (output_color_transform) { - // Yes color transform, blit color managed - ColorTransformJob job; + // No color transform, just blit + ShaderJob job; + job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture))); + job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, matrix)); - job.SetColorProcessor(output_color_transform); - job.SetInputTexture(texture); - job.SetInputAlphaAssociation(OLIVE_CONFIG("ReassocLinToNonLin").toBool() ? kAlphaAssociated : kAlphaNone); - job.SetTransformMatrix(matrix); - - render_ctx_->BlitColorManaged(job, blit_tex.get()); - } else { - // No color transform, just blit - ShaderJob job; - job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture))); - job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, matrix)); - - render_ctx_->BlitToTexture(default_shader_, job, blit_tex.get()); - } + render_ctx_->BlitToTexture(default_shader_, job, blit_tex.get()); // Replace texture that we're going to download in the next step texture = blit_tex; @@ -179,8 +179,8 @@ void RenderProcessor::Run() // Save to cache if requested if (!cache.isEmpty()) { rational timebase = ticket_->property("cachetimebase").value(); - QUuid uuid = ticket_->property("cacheuuid").value(); - bool cache_result = FrameHashCache::SaveCacheFrame(cache, uuid, time, timebase, frame); + QString id = ticket_->property("cacheid").toString(); + bool cache_result = FrameHashCache::SaveCacheFrame(cache, id, time, timebase, frame); ticket_->setProperty("cached", cache_result); } } @@ -438,7 +438,7 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ bool frame = decoder->RetrieveVideo(unmanaged_texture, (stream_data.video_type() == VideoParams::kVideoTypeVideo) ? input_time : Decoder::kAnyTimecode, p, GetCancelPointer()); - if (frame) { + if (!IsCancelled() && frame) { // We convert to our rendering pixel format, since that will always be float-based which // is necessary for correct color conversion ColorProcessorPtr processor = ColorProcessor::Create(color_manager, diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index c40ff3684..1cea23b29 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -235,6 +235,21 @@ QString VideoParams::GetFormatName(VideoParams::Format format) return QCoreApplication::translate("VideoParams", "Unknown (0x%1)").arg(format, 0, 16); } +int VideoParams::GetDividerForTargetResolution(int src_width, int src_height, int dst_width, int dst_height) +{ + int divider = 0; + int test_width, test_height; + + do { + divider++; + + test_width = VideoParams::GetScaledDimension(src_width, divider); + test_height = VideoParams::GetScaledDimension(src_height, divider); + } while (test_width > dst_width || test_height > dst_height); + + return divider; +} + void VideoParams::calculate_effective_size() { effective_width_ = GetScaledDimension(width(), divider_); diff --git a/app/render/videoparams.h b/app/render/videoparams.h index 564985dc4..fcb1e804c 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -235,6 +235,8 @@ public: static QString GetFormatName(Format format); + static int GetDividerForTargetResolution(int src_width, int src_height, int dst_width, int dst_height); + static const int kInternalChannelCount; static const rational kPixelAspectSquare; diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 2968f6d5a..df2148cab 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -58,19 +58,15 @@ bool RenderTask::Render(ColorManager* manager, // 50%, which makes the progress bar look weird to the uninitiated //total_length += r.length().toDouble(); - rational r = range.in(); - while (r != range.out()) { - rational end = qMin(range.out(), r+1); - TimeRange this_range(r, end); + RenderManager::RenderAudioParams rap(viewer_->GetConnectedSampleOutput(), + range, + audio_params_); - RenderTicketWatcher* watcher = new RenderTicketWatcher(); - watcher->setProperty("range", QVariant::fromValue(this_range)); - PrepareWatcher(watcher, &watcher_thread); - IncrementRunningTickets(); - watcher->SetTicket(RenderManager::instance()->RenderAudio(viewer_->GetConnectedSampleOutput(), this_range, audio_params_, mode, false)); - - r = end; - } + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + watcher->setProperty("range", QVariant::fromValue(range)); + PrepareWatcher(watcher, &watcher_thread); + IncrementRunningTickets(); + watcher->SetTicket(RenderManager::instance()->RenderAudio(rap)); } // Look up hashes @@ -277,15 +273,23 @@ void RenderTask::StartTicket(QThread* watcher_thread, ColorManager* manager, const QSize &force_size, const QMatrix4x4 &force_matrix, VideoParams::Format force_format, ColorProcessorPtr force_color_output) { + RenderManager::RenderVideoParams rvp(viewer_->GetConnectedTextureOutput(), video_params_, audio_params_, + time, manager); + + rvp.force_size = force_size; + rvp.force_matrix = force_matrix; + rvp.force_format = force_format; + rvp.force_color_output = force_color_output; + + if (cache) { + rvp.AddCache(cache); + } + RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("time", QVariant::fromValue(time)); PrepareWatcher(watcher, watcher_thread); IncrementRunningTickets(); - watcher->SetTicket(RenderManager::instance()->RenderFrame(viewer_->GetConnectedTextureOutput(), manager, time, - mode, video_params_, audio_params_, - force_size, force_matrix, - force_format, force_color_output, - cache)); + watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp)); } void RenderTask::TicketDone(RenderTicketWatcher* watcher) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index aeb1a0678..59a61addc 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1093,6 +1093,11 @@ void TimelineWidget::ShowContextMenu() toggle_audio_units->setChecked(use_audio_time_units_); connect(toggle_audio_units, &QAction::triggered, this, &TimelineWidget::SetUseAudioTimeUnits); + QAction* show_thumbnails = menu.addAction(tr("Show Thumbnails")); + show_thumbnails->setCheckable(true); + show_thumbnails->setChecked(views_.first()->view()->GetShowThumbnails()); + connect(show_thumbnails, &QAction::triggered, this, &TimelineWidget::SetViewThumbnailsEnabled); + QAction* show_waveforms = menu.addAction(tr("Show Waveforms")); show_waveforms->setCheckable(true); show_waveforms->setChecked(views_.first()->view()->GetShowWaveforms()); @@ -1167,6 +1172,13 @@ void TimelineWidget::SetViewWaveformsEnabled(bool e) } } +void TimelineWidget::SetViewThumbnailsEnabled(bool e) +{ + foreach (TimelineAndTrackView* tview, views_) { + tview->view()->SetShowThumbnails(e); + } +} + void TimelineWidget::FrameRateChanged() { SetTimebase(GetConnectedNode()->GetVideoParams().frame_rate_as_time_base()); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index c7a38dfaa..c1ec0b292 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -415,6 +415,8 @@ private slots: void SetViewWaveformsEnabled(bool e); + void SetViewThumbnailsEnabled(bool e); + void FrameRateChanged(); void SampleRateChanged(); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index f7fd1a637..6ab6ea736 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -524,18 +524,27 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q // Draw clip thumbnails if (clip->GetTrackType() == Track::kVideo && show_thumbnails_ && preview_rect.height() > r.height()/3) { - const int kTempThumbWidth = 120; - const int kTempThumbHeight = 68; + if (const FrameHashCache *thumbs = clip->thumbnails()) { + QRect thumb_rect; + painter->setClipRect(preview_rect); + painter->setRenderHint(QPainter::SmoothPixmapTransform); + for (int i=preview_rect.left(); iparent()->GetAudioParams().sample_rate_as_time_base()) + media_in; + QString thumbnail = thumbs->GetValidCacheFilename(time_here); - QRect thumb_rect; - painter->setClipRect(preview_rect); - for (int i=preview_rect.left(); ifillRect(thumb_rect, Qt::red); + if (thumbnail.isEmpty()) { + break; + } else { + QImage img; + if (img.load(thumbnail, "jpg")) { + double scale = double(preview_rect.height())/double(img.height()); + thumb_rect = QRect(i, preview_rect.top(), img.width() * scale, preview_rect.height()); + painter->drawImage(thumb_rect, img); + } + } + } + painter->setClipping(false); } - painter->setClipping(false); } // Draw waveform diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 28a8cb799..9dea755f2 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -84,6 +84,17 @@ public: viewport()->update(); } + bool GetShowThumbnails() const + { + return show_thumbnails_; + } + + void SetShowThumbnails(bool e) + { + show_thumbnails_ = e; + viewport()->update(); + } + signals: void MousePressed(TimelineViewMouseEvent* event); void MouseMoved(TimelineViewMouseEvent* event); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 5d6980bcb..7ee96fc20 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -148,6 +148,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : setAcceptDrops(true); auto_cacher_ = new PreviewAutoCacher(this); + connect(display_widget_, &ViewerDisplayWidget::ColorProcessorChanged, auto_cacher_, &PreviewAutoCacher::SetDisplayColorProcessor); connect(Core::instance(), &Core::ColorPickerEnabled, this, &ViewerWidget::SetSignalCursorColorEnabled); connect(this, &ViewerWidget::CursorColor, Core::instance(), &Core::ColorPickerColorEmitted); @@ -686,11 +687,6 @@ void ViewerWidget::UpdateTextureFromNode() connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrame); nonqueue_watchers_.append(watcher); - // Clear queue because we want this frame more than any others - if (!GetConnectedNode()->video_frame_cache()->IsAutomatic() && !auto_cacher_->IsRenderingCustomRange()) { - ClearVideoAutoCacherQueue(); - } - watcher->SetTicket(GetFrame(time, RenderTicketPriority::kHigh)); } else { // There is definitely no frame here, we can immediately flip to showing nothing @@ -718,8 +714,8 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) foreach (ViewerWidget* viewer, instances_) { if (viewer != this) { viewer->PauseInternal(); - viewer->auto_cacher_->SetRendersPaused(true); } + viewer->auto_cacher_->SetRendersPaused(true); } // Disarm recording if armed From 3d8c0114fbe9f24b476749391b4aca338173e2ae Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 27 May 2022 11:20:38 -0700 Subject: [PATCH 07/53] render: remove unused function --- app/node/traverser.h | 5 ----- app/render/renderprocessor.cpp | 5 ----- app/render/renderprocessor.h | 2 -- 3 files changed, 12 deletions(-) diff --git a/app/node/traverser.h b/app/node/traverser.h index e129096ab..fca5083f3 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -121,11 +121,6 @@ protected: } } - virtual bool CanCacheFrames() - { - return false; - } - QVector2D GenerateResolution() const; bool IsCancelled() diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index aa38a9745..970e48001 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -556,11 +556,6 @@ void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node destination->Upload(frame->data(), frame->linesize_pixels()); } -bool RenderProcessor::CanCacheFrames() -{ - return ticket_->property("type").value() == RenderManager::kTypeVideo; -} - void RenderProcessor::ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs) { ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager")); diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 1fb6ea23f..9c0ad40cd 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -56,8 +56,6 @@ protected: virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job) override; - virtual bool CanCacheFrames() override; - virtual TexturePtr CreateTexture(const VideoParams &p) override { return render_ctx_->CreateTexture(p); From e9404ba869d72b8a831e8aba137b999eb781e57c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 27 May 2022 19:16:08 -0700 Subject: [PATCH 08/53] added second frame cache for thumbnails --- app/node/block/clip/clip.cpp | 6 +-- app/node/block/clip/clip.h | 2 +- app/node/node.cpp | 2 + app/node/node.h | 6 +++ app/render/framehashcache.cpp | 41 --------------- app/render/framehashcache.h | 3 -- app/render/previewautocacher.cpp | 52 +++++++++++++------ app/render/previewautocacher.h | 3 +- app/widget/timebased/timescaledobject.cpp | 3 ++ .../timelinewidget/view/timelineview.cpp | 13 ++++- 10 files changed, 64 insertions(+), 67 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 696f900ae..8e15a15e3 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -187,7 +187,7 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int if (Node *connected = GetConnectedOutput(from, element)) { TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, length())); if (type == Track::kVideo) { - emit connected->video_frame_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + emit connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); } else if (type == Track::kAudio) { emit connected->audio_playback_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); } @@ -250,7 +250,7 @@ void ClipBlock::InputConnectedEvent(const QString &input, int element, Node *out super::InputConnectedEvent(input, element, output); if (input == kBufferIn) { - connect(output->video_frame_cache(), &FrameHashCache::ThumbnailsUpdated, this, &Block::PreviewChanged); + connect(output->thumbnail_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); connect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); } } @@ -260,7 +260,7 @@ void ClipBlock::InputDisconnectedEvent(const QString &input, int element, Node * super::InputDisconnectedEvent(input, element, output); if (input == kBufferIn) { - disconnect(output->video_frame_cache(), &FrameHashCache::ThumbnailsUpdated, this, &Block::PreviewChanged); + disconnect(output->thumbnail_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); disconnect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index dab1ecf41..a725f147f 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -122,7 +122,7 @@ public: const FrameHashCache *thumbnails() { if (Node *n = GetConnectedOutput(kBufferIn)) { - return n->video_frame_cache(); + return n->thumbnail_cache(); } else { return nullptr; } diff --git a/app/node/node.cpp b/app/node/node.cpp index b931cf504..8f18fb996 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -54,6 +54,7 @@ Node::Node() : AddInput(kEnabledInput, NodeValue::kBoolean, true); video_cache_ = new FrameHashCache(this); + thumbnail_cache_ = new FrameHashCache(this); audio_cache_ = new AudioPlaybackCache(this); } @@ -936,6 +937,7 @@ void Node::InvalidateCache(const TimeRange &range, const QString &from, int elem TimeRange vr = range.Intersected(GetVideoCacheRange()); if (vr.length() != 0) { video_frame_cache()->Invalidate(vr); + thumbnail_cache()->Invalidate(vr); } TimeRange ar = range.Intersected(GetAudioCacheRange()); if (ar.length() != 0) { diff --git a/app/node/node.h b/app/node/node.h index dd38040d4..0baf318d0 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -226,6 +226,11 @@ public: return video_cache_; } + FrameHashCache* thumbnail_cache() const + { + return thumbnail_cache_; + } + AudioPlaybackCache* audio_playback_cache() const { return audio_cache_; @@ -1402,6 +1407,7 @@ private: QString effect_input_; FrameHashCache *video_cache_; + FrameHashCache *thumbnail_cache_; AudioPlaybackCache *audio_cache_; diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index ad0832459..86f3dbba1 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -340,47 +340,6 @@ bool FrameHashCache::SaveCacheFrame(const QString &filename, const FramePtr fram QImage img(reinterpret_cast(frame->data()), frame->width(), frame->height(), frame->linesize_bytes(), fmt); return img.save(filename, "jpg"); - - - /* - qDebug() << "hello?" << filename; - - // Integer types are stored in JPG - QString tmp = filename; - tmp.append(QStringLiteral(".jpg")); - - std::string tmp_std = tmp.toStdString(); - auto out = OIIO::ImageOutput::create(tmp_std); - if (!out) { - qDebug() << "fail create"; - return false; - } - - auto fmt = OIIOUtils::GetOIIOBaseTypeFromFormat(frame->format()); - qDebug() << "writing" << fmt; - if (!out->open(tmp_std, OIIO::ImageSpec(frame->width(), frame->height(), frame->channel_count(), fmt))) { - qDebug() << "fail open"; - return false; - } - - bool ret = out->write_image(fmt, frame->data(), OIIO::AutoStride, frame->linesize_bytes()); - out->close(); - - if (ret) { - QFile f(filename); - if (f.exists()) { - f.remove(); - } - ret = QFile::rename(tmp, filename); - if (!ret) { - qDebug() << "fail rename from" << tmp << "to" << filename; - } - } else { - qDebug() << "fail write"; - } - - return ret; - */ } } diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index d45228a7c..71d53223d 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -65,9 +65,6 @@ public: FramePtr LoadCacheFrame(const int64_t &time) const; static FramePtr LoadCacheFrame(const QString& fn); -signals: - void ThumbnailsUpdated(); - private: rational ToTime(const int64_t &ts) const; int64_t ToTimestamp(const rational &ts, Timecode::Rounding rounding = Timecode::kRound) const; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 36d60b099..d404fac28 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -83,11 +83,18 @@ RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, RenderTicket return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, PlaybackCache::kCacheOnly, priority); } -void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range, PlaybackCache::RequestType type) +void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range) { FrameHashCache *cache = static_cast(sender()); - VideoInvalidatedFromNode(cache->parent(), range, type); + VideoInvalidatedFromNode(cache->parent(), range, PlaybackCache::kCacheOnly); +} + +void PreviewAutoCacher::ThumbnailsInvalidatedFromCache(const TimeRange &range) +{ + FrameHashCache *cache = static_cast(sender()); + + VideoInvalidatedFromNode(cache->parent(), range, PlaybackCache::kPreviewsOnly); } void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range, PlaybackCache::RequestType type) @@ -162,17 +169,9 @@ void PreviewAutoCacher::VideoRendered() if (it != video_tasks_.end()) { // Assume that a "result" is a fully completed image and a non-result is a cancelled ticket if (watcher->HasResult()) { - PlaybackCache::RequestType type = PlaybackCache::RequestType(watcher->property("type").toInt()); - - if (type == PlaybackCache::kCacheOnly) { - if (watcher->GetTicket()->property("cached").toBool()) { - if (FrameHashCache *cache = Node::ValueToPtr(watcher->property("cache"))) { - cache->ValidateTime(it.value()); - } - } - } else { + if (watcher->GetTicket()->property("cached").toBool()) { if (FrameHashCache *cache = Node::ValueToPtr(watcher->property("cache"))) { - emit cache->ThumbnailsUpdated(); + cache->ValidateTime(it.value()); } } } @@ -336,6 +335,11 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node) this, &PreviewAutoCacher::VideoInvalidatedFromCache); + connect(node->thumbnail_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::ThumbnailsInvalidatedFromCache); + connect(node->audio_playback_cache(), &PlaybackCache::Request, this, @@ -368,6 +372,11 @@ void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) this, &PreviewAutoCacher::VideoInvalidatedFromCache); + disconnect(node->thumbnail_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::ThumbnailsInvalidatedFromCache); + disconnect(node->audio_playback_cache(), &PlaybackCache::Request, this, @@ -606,7 +615,15 @@ void PreviewAutoCacher::TryRender() // We want this hash, if we're not already rendering, start render now if (!render_task) { // Don't render any hash more than once - RenderFrame(copy, t, d.type, RenderTicketPriority::kNormal, d.node->video_frame_cache()); + FrameHashCache *using_cache; + + if (d.type == PlaybackCache::kCacheOnly) { + using_cache = d.node->video_frame_cache(); + } else { + using_cache = d.node->thumbnail_cache(); + } + + RenderFrame(copy, t, d.type, RenderTicketPriority::kNormal, using_cache); } emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); @@ -658,14 +675,17 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& copied_color_manager_); if (cache) { - cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); - rvp.AddCache(cache); - if (type == PlaybackCache::kPreviewsOnly) { rvp.video_params.set_divider(VideoParams::GetDividerForTargetResolution(rvp.video_params.width(), rvp.video_params.height(), 160, 120)); rvp.force_color_output = display_color_processor_; rvp.force_format = VideoParams::kFormatUnsigned8; + + cache->SetTimebase(rational(1, 10)); + } else { + cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); } + + rvp.AddCache(cache); } rvp.priority = priority; diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 933ac5033..f987dfe46 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -227,7 +227,8 @@ private slots: /** * @brief Handler for when the NodeGraph reports a video change over a certain time range */ - void VideoInvalidatedFromCache(const olive::TimeRange &range, olive::PlaybackCache::RequestType type); + void VideoInvalidatedFromCache(const olive::TimeRange &range); + void ThumbnailsInvalidatedFromCache(const olive::TimeRange &range); /** * @brief Handler for when the NodeGraph reports a audio change over a certain time range diff --git a/app/widget/timebased/timescaledobject.cpp b/app/widget/timebased/timescaledobject.cpp index daefa14d2..1102f31ed 100644 --- a/app/widget/timebased/timescaledobject.cpp +++ b/app/widget/timebased/timescaledobject.cpp @@ -64,6 +64,9 @@ rational TimeScaledObject::SceneToTime(const double &x, const double &x_scale, c if (round) { rounded_x_mvmt = qRound64(unscaled_time); + } else if (unscaled_time < 0) { + // "floor" to zero + rounded_x_mvmt = qCeil(unscaled_time); } else { rounded_x_mvmt = qFloor(unscaled_time); } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 6ab6ea736..a11a449ba 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -529,11 +529,20 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q painter->setClipRect(preview_rect); painter->setRenderHint(QPainter::SmoothPixmapTransform); for (int i=preview_rect.left(); iparent()->GetAudioParams().sample_rate_as_time_base()) + media_in; + rational time_here = SceneToTime(i - block_in, GetScale(), connected_track_list_->parent()->GetVideoParams().frame_rate_as_time_base()) + media_in; QString thumbnail = thumbs->GetValidCacheFilename(time_here); if (thumbnail.isEmpty()) { - break; + // Jump ahead to next frame, ensuring that frame width > 0 for optimization + if (thumb_rect.width() == 0 && clip->track() && clip->track()->sequence()) { + Sequence *s = clip->track()->sequence(); + int width = s->GetVideoParams().width(); + int height = s->GetVideoParams().height(); + if (height > 0) { // Prevent divide by zero/invalid params + double scale = double(preview_rect.height())/double(height); + thumb_rect.setWidth(width * scale); + } + } } else { QImage img; if (img.load(thumbnail, "jpg")) { From 826090b62199a24b1734f777db2a29b4c0b90ff9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 29 May 2022 14:03:09 -0700 Subject: [PATCH 09/53] cache: save states to disk --- app/node/block/clip/clip.cpp | 19 +++++ app/node/block/clip/clip.h | 2 + app/node/node.h | 1 + .../project/serializer/serializer220403.cpp | 20 ++++++ app/render/framehashcache.cpp | 26 +++++++ app/render/framehashcache.h | 4 ++ app/render/playbackcache.cpp | 71 +++++++++++++++++++ app/render/playbackcache.h | 7 ++ app/render/previewautocacher.cpp | 11 +++ app/widget/viewer/viewer.cpp | 2 +- 10 files changed, 162 insertions(+), 1 deletion(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 8e15a15e3..e0aaa25bf 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -311,4 +311,23 @@ void ClipBlock::Retranslate() SetInputName(kMaintainAudioPitchInput, tr("Maintain Audio Pitch")); } +void ClipBlock::ConnectedToPreviewEvent() +{ + Track::Type type = GetTrackType(); + + if (type == Track::kVideo || type == Track::kAudio) { + if (Node *connected = GetConnectedOutput(kBufferIn)) { + TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); + if (type == Track::kVideo) { + TimeRangeList invalid = connected->thumbnail_cache()->GetInvalidatedRanges(max_range); + for (const TimeRange &r : invalid) { + emit connected->thumbnail_cache()->Request(r, PlaybackCache::kPreviewsOnly); + } + } else if (type == Track::kAudio) { + //emit connected->audio_playback_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + } + } + } +} + } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index a725f147f..feff9c4d2 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -158,6 +158,8 @@ public: return TimeRange(0, length()); } + virtual void ConnectedToPreviewEvent() override; + static const QString kBufferIn; static const QString kMediaInInput; static const QString kSpeedInput; diff --git a/app/node/node.h b/app/node/node.h index 324af8089..13beb3d1c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -991,6 +991,7 @@ public: void SetInputFlags(const QString &input, const InputFlags &f); virtual void LoadFinishedEvent(){} + virtual void ConnectedToPreviewEvent(){} static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 2075b6ce8..00e5dc25b 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -544,6 +544,18 @@ void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, Q reader->skipCurrentElement(); } } + } else if (reader->name() == QStringLiteral("caches")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("audio")) { + node->audio_playback_cache()->SetUuid(reader->readElementText()); + } else if (reader->name() == QStringLiteral("video")) { + node->video_frame_cache()->SetUuid(reader->readElementText()); + } else if (reader->name() == QStringLiteral("thumb")) { + node->thumbnail_cache()->SetUuid(reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } } else { reader->skipCurrentElement(); } @@ -599,6 +611,14 @@ void ProjectSerializer220403::SaveNode(Node *node, QXmlStreamWriter *writer) con } writer->writeEndElement(); + writer->writeStartElement(QStringLiteral("caches")); + + writer->writeTextElement(QStringLiteral("audio"), node->audio_playback_cache()->GetUuid().toString()); + writer->writeTextElement(QStringLiteral("video"), node->video_frame_cache()->GetUuid().toString()); + writer->writeTextElement(QStringLiteral("thumb"), node->thumbnail_cache()->GetUuid().toString()); + + writer->writeEndElement(); // caches + writer->writeStartElement(QStringLiteral("custom")); SaveNodeCustom(writer, node); diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index 86f3dbba1..decdbcc3d 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -193,6 +193,32 @@ FramePtr FrameHashCache::LoadCacheFrame(const QString &fn) return frame; } +void FrameHashCache::LoadStateEvent(QDataStream &stream) +{ + uint32_t version; + int num, den; + + stream >> version; + + switch (version) { + case 1: + stream >> num; + stream >> den; + timebase_ = rational(num, den); + break; + } +} + +void FrameHashCache::SaveStateEvent(QDataStream &stream) +{ + uint32_t version = 1; + + stream << version; + + stream << timebase_.numerator(); + stream << timebase_.denominator(); +} + rational FrameHashCache::ToTime(const int64_t &ts) const { return Timecode::timestamp_to_time(ts, timebase_); diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index 71d53223d..460d33fee 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -65,6 +65,10 @@ public: FramePtr LoadCacheFrame(const int64_t &time) const; static FramePtr LoadCacheFrame(const QString& fn); +protected: + virtual void LoadStateEvent(QDataStream &stream) override; + virtual void SaveStateEvent(QDataStream &stream) override; + private: rational ToTime(const int64_t &ts) const; int64_t ToTimestamp(const rational &ts, Timecode::Rounding rounding = Timecode::kRound) const; diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 762393223..3d5db2258 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -60,6 +60,77 @@ QDir PlaybackCache::GetThisCacheDirectory(const QString &cache_path, const QUuid return QDir(cache_path).filePath(cache_id.toString()); } +void PlaybackCache::LoadState() +{ + QDir cache_dir = GetThisCacheDirectory(); + QFile f(cache_dir.filePath(QStringLiteral("state"))); + if (f.open(QFile::ReadOnly)) { + QDataStream s(&f); + + uint32_t version; + s >> version; + + LoadStateEvent(s); + + int count; + s >> count; + + switch (version) { + case 1: + validated_.clear(); + + for (int i=0; i> in_num; + s >> in_den; + s >> out_num; + s >> out_den; + + validated_.insert(TimeRange(rational(in_num, in_den), rational(out_num, out_den))); + } + break; + } + + f.close(); + + f.close(); + } +} + +void PlaybackCache::SaveState() +{ + QDir cache_dir = GetThisCacheDirectory(); + QFile f(cache_dir.filePath(QStringLiteral("state"))); + if (validated_.isEmpty()) { + if (f.exists()) { + f.remove(); + } + } else { + if (FileFunctions::DirectoryIsValid(cache_dir)) { + if (f.open(QFile::WriteOnly)) { + QDataStream s(&f); + + uint32_t version = 1; + s << version; + + SaveStateEvent(s); + + s << validated_.size(); + + for (const TimeRange &r : validated_) { + s << r.in().numerator(); + s << r.in().denominator(); + s << r.out().numerator(); + s << r.out().denominator(); + } + + f.close(); + } + } + } +} + void PlaybackCache::InvalidateAll() { Invalidate(TimeRange(0, RATIONAL_MAX)); diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index 64045257b..e5c7db383 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -74,6 +74,9 @@ public: kPreviewsOnly }; + void LoadState(); + void SaveState(); + public slots: void InvalidateAll(); @@ -91,6 +94,10 @@ protected: virtual void InvalidateEvent(const TimeRange& range); + virtual void LoadStateEvent(QDataStream &stream){} + + virtual void SaveStateEvent(QDataStream &stream){} + Project* GetProject() const; private: diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 3e8b35e16..ef48d6284 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -345,6 +345,10 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node) this, &PreviewAutoCacher::AudioInvalidatedFromCache); + node->video_frame_cache()->LoadState(); + node->audio_playback_cache()->LoadState(); + node->thumbnail_cache()->LoadState(); + // Copy invalidated ranges and start rendering if necessary if (node->video_frame_cache()->IsAutomatic()) { VideoAutoCacheEnableChangedFromNode(node, true); @@ -382,6 +386,10 @@ void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) this, &PreviewAutoCacher::AudioInvalidatedFromCache); + node->video_frame_cache()->SaveState(); + node->audio_playback_cache()->SaveState(); + node->thumbnail_cache()->SaveState(); + if (node->video_frame_cache()->IsAutomatic()) { VideoAutoCacheEnableChangedFromNode(node, false); } @@ -841,6 +849,9 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) for (int i=copied_project_.nodes().size(); inodes().size(); i++) { AddNode(graph->nodes().at(i)); } + for (int i=0; inodes().size(); i++) { + graph->nodes().at(i)->ConnectedToPreviewEvent(); + } // Find copied viewer node copied_viewer_node_ = static_cast(copy_map_.value(viewer_node_)); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 432f3b3ca..d0ad7d6e0 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -837,7 +837,7 @@ void ViewerWidget::PushScrubbedAudio() { if (!IsPlaying() && GetConnectedNode() && OLIVE_CONFIG("AudioScrubbing").toBool() && enable_audio_scrubbing_) { // Get audio src device from renderer - const AudioParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters(); + const AudioParams& params = GetConnectedNode()->GetAudioParams(); if (params.is_valid()) { // NOTE: Hardcoded scrubbing interval (20ms) From 2f7dc3c98b6c571a5c0cfcf3ee44c5d82d4d122d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 29 May 2022 17:52:35 -0700 Subject: [PATCH 10/53] implemented separate waveform cache --- app/audio/audiovisualwaveform.cpp | 68 ++++++----- app/audio/audiovisualwaveform.h | 7 +- app/node/block/clip/clip.cpp | 20 +++- app/node/block/clip/clip.h | 6 +- app/node/node.cpp | 1 + app/node/node.h | 7 ++ app/node/output/viewer/viewer.cpp | 35 +++++- app/node/output/viewer/viewer.h | 16 +++ app/node/project/footage/footage.cpp | 2 + .../project/serializer/serializer220403.cpp | 3 + app/render/CMakeLists.txt | 2 + app/render/audioplaybackcache.cpp | 20 ---- app/render/audioplaybackcache.h | 12 -- app/render/audiowaveformcache.cpp | 101 +++++++++++++++++ app/render/audiowaveformcache.h | 85 ++++++++++++++ app/render/previewautocacher.cpp | 106 +++++------------- app/render/previewautocacher.h | 9 +- app/render/rendermanager.cpp | 1 + app/render/rendermanager.h | 2 + app/render/renderprocessor.cpp | 4 +- app/widget/audiomonitor/audiomonitor.cpp | 7 +- app/widget/audiomonitor/audiomonitor.h | 9 +- app/widget/timelinewidget/tool/pointer.cpp | 3 +- .../timelinewidget/undo/timelineundosplit.cpp | 5 +- .../timelinewidget/view/timelineview.cpp | 4 +- app/widget/viewer/audiowaveformview.cpp | 16 ++- app/widget/viewer/audiowaveformview.h | 4 +- app/widget/viewer/viewer.cpp | 4 +- 28 files changed, 381 insertions(+), 178 deletions(-) create mode 100644 app/render/audiowaveformcache.cpp create mode 100644 app/render/audiowaveformcache.h diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 0fb9c215a..c9b2ef50e 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -193,38 +193,12 @@ void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational } } -void AudioVisualWaveform::Shift(const rational &from, const rational &to) -{ - for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { - rational rate = it->first; - double rate_dbl = rate.toDouble(); - Sample& data = it->second; - - int from_index = time_to_samples(from, rate_dbl); - int to_index = time_to_samples(to, rate_dbl); - - if (from_index == to_index) { - continue; - } - - if (from_index >= data.size()) { - continue; - } - - if (from_index > to_index) { - // Shifting backwards <- - data.remove(to_index, from_index - to_index); - } else { - // Shifting forwards -> - data.insert(from_index, to_index - from_index, {0, 0}); - } - } - - length_ = qMax(rational(0), length_ + (to-from)); -} - void AudioVisualWaveform::TrimIn(const rational &length) { + if (length == 0) { + return; + } + for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { rational rate = it->first; double rate_dbl = rate.toDouble(); @@ -255,6 +229,40 @@ AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset) const return mid; } +AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset, const rational &length) const +{ + AudioVisualWaveform mid = *this; + + mid.TrimRange(offset, length); + + return mid; +} + +void AudioVisualWaveform::Resize(const rational &length) +{ + if (length_ == length) { + return; + } + + for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { + rational rate = it->first; + double rate_dbl = rate.toDouble(); + Sample& data = it->second; + + int chop_length = time_to_samples(length, rate_dbl); + + data.resize(chop_length); + } + + length_ = length; +} + +void AudioVisualWaveform::TrimRange(const rational &in, const rational &length) +{ + TrimIn(in); + Resize(length); +} + AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const rational &start, const rational &length) const { // Find mipmap that requires diff --git a/app/audio/audiovisualwaveform.h b/app/audio/audiovisualwaveform.h index 389f72980..e2202b469 100644 --- a/app/audio/audiovisualwaveform.h +++ b/app/audio/audiovisualwaveform.h @@ -90,11 +90,14 @@ public: void OverwriteSilence(const rational &start, const rational &length); - void Shift(const rational& from, const rational& to); - void TrimIn(const rational &length); AudioVisualWaveform Mid(const rational &offset) const; + AudioVisualWaveform Mid(const rational &offset, const rational &length) const; + + void Resize(const rational &length); + + void TrimRange(const rational &in, const rational &length); Sample GetSummaryFromTime(const rational& start, const rational& length) const; diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index e0aaa25bf..6a61b73ac 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -38,7 +38,8 @@ const QString ClipBlock::kMaintainAudioPitchInput = QStringLiteral("maintain_aud ClipBlock::ClipBlock() : in_transition_(nullptr), out_transition_(nullptr), - connected_viewer_(nullptr) + connected_viewer_(nullptr), + autocache_(false) { AddInput(kMediaInInput, NodeValue::kRational, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); SetInputProperty(kMediaInInput, QStringLiteral("view"), RationalSlider::kTime); @@ -188,8 +189,14 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, length())); if (type == Track::kVideo) { emit connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + if (autocache_) { + emit connected->video_frame_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + } } else if (type == Track::kAudio) { - emit connected->audio_playback_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + emit connected->waveform_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + if (autocache_) { + emit connected->audio_playback_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + } } } } @@ -251,7 +258,7 @@ void ClipBlock::InputConnectedEvent(const QString &input, int element, Node *out if (input == kBufferIn) { connect(output->thumbnail_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); - connect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); + connect(output->waveform_cache(), &AudioPlaybackCache::Validated, this, &Block::PreviewChanged); } } @@ -261,7 +268,7 @@ void ClipBlock::InputDisconnectedEvent(const QString &input, int element, Node * if (input == kBufferIn) { disconnect(output->thumbnail_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); - disconnect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); + disconnect(output->waveform_cache(), &AudioPlaybackCache::Validated, this, &Block::PreviewChanged); } } @@ -324,7 +331,10 @@ void ClipBlock::ConnectedToPreviewEvent() emit connected->thumbnail_cache()->Request(r, PlaybackCache::kPreviewsOnly); } } else if (type == Track::kAudio) { - //emit connected->audio_playback_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); + for (const TimeRange &r : invalid) { + emit connected->waveform_cache()->Request(r, PlaybackCache::kPreviewsOnly); + } } } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index feff9c4d2..b43bfde09 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -128,10 +128,10 @@ public: } } - const AudioVisualWaveform *waveform() + const AudioWaveformCache *waveform() { if (Node *n = GetConnectedOutput(kBufferIn)) { - return &n->audio_playback_cache()->visual(); + return n->waveform_cache(); } else { return nullptr; } @@ -185,6 +185,8 @@ private: ViewerOutput *connected_viewer_; + bool autocache_; + private: rational last_media_in_; diff --git a/app/node/node.cpp b/app/node/node.cpp index 8f18fb996..7d3bc7345 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -56,6 +56,7 @@ Node::Node() : video_cache_ = new FrameHashCache(this); thumbnail_cache_ = new FrameHashCache(this); audio_cache_ = new AudioPlaybackCache(this); + waveform_cache_ = new AudioWaveformCache(this); } Node::~Node() diff --git a/app/node/node.h b/app/node/node.h index 13beb3d1c..6a96f97a1 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -40,6 +40,7 @@ #include "node/param.h" #include "render/audioparams.h" #include "render/audioplaybackcache.h" +#include "render/audiowaveformcache.h" #include "render/framehashcache.h" #include "render/job/generatejob.h" #include "render/job/samplejob.h" @@ -236,6 +237,11 @@ public: return audio_cache_; } + AudioWaveformCache* waveform_cache() const + { + return waveform_cache_; + } + virtual TimeRange GetVideoCacheRange() const { return TimeRange(); } virtual TimeRange GetAudioCacheRange() const { return TimeRange(); } @@ -1413,6 +1419,7 @@ private: FrameHashCache *thumbnail_cache_; AudioPlaybackCache *audio_cache_; + AudioWaveformCache *waveform_cache_; private slots: /** diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 9330e414d..84492de8f 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -37,7 +37,9 @@ const QString ViewerOutput::kSamplesInput = QStringLiteral("samples_in"); ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_streams) : last_length_(0), video_length_(0), - audio_length_(0) + audio_length_(0), + autocache_input_video_(false), + autocache_input_audio_(false) { AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); @@ -219,6 +221,22 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, { Q_UNUSED(element) + if (Node *connected = GetConnectedOutput(from, element)) { + if (from == kTextureInput) { + //emit 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), PlaybackCache::kPreviewsOnly); + } + } else if (from == kSamplesInput) { + TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength())); + emit connected->waveform_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + if (autocache_input_audio_) { + emit connected->audio_playback_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + } + } + } + VerifyLength(); super::InvalidateCache(range, from, element, options); @@ -298,6 +316,8 @@ void ViewerOutput::InputConnectedEvent(const QString &input, int element, Node * { if (input == kTextureInput) { emit TextureInputChanged(); + } else if (input == kSamplesInput) { + connect(output->waveform_cache(), &AudioWaveformCache::Validated, this, &ViewerOutput::ConnectedWaveformChanged); } super::InputConnectedEvent(input, element, output); @@ -307,6 +327,8 @@ void ViewerOutput::InputDisconnectedEvent(const QString &input, int element, Nod { if (input == kTextureInput) { emit TextureInputChanged(); + } else if (input == kSamplesInput) { + disconnect(output->waveform_cache(), &AudioWaveformCache::Validated, this, &ViewerOutput::ConnectedWaveformChanged); } super::InputDisconnectedEvent(input, element, output); @@ -364,6 +386,17 @@ Node::ValueHint ViewerOutput::GetConnectedSampleValueHint() return GetValueHintForInput(kSamplesInput); } +void ViewerOutput::ConnectedToPreviewEvent() +{ + if (Node *connected = GetConnectedOutput(kSamplesInput)) { + 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, PlaybackCache::kPreviewsOnly); + } + } +} + void ViewerOutput::InputValueChangedEvent(const QString &input, int element) { if (element == 0) { diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index dff2e8e7c..daca1d4a4 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -130,6 +130,15 @@ public: return GetVideoStreamCount() + GetAudioStreamCount() + GetSubtitleStreamCount(); } + const AudioWaveformCache *GetConnectedWaveform() + { + if (Node *n = GetConnectedSampleOutput()) { + return n->waveform_cache(); + } else { + return nullptr; + } + } + bool HasEnabledVideoStreams() const; bool HasEnabledAudioStreams() const; bool HasEnabledSubtitleStreams() const; @@ -173,6 +182,8 @@ public: virtual ValueHint GetConnectedSampleValueHint(); + virtual void ConnectedToPreviewEvent() override; + static const QString kVideoParamsInput; static const QString kAudioParamsInput; static const QString kSubtitleParamsInput; @@ -198,6 +209,8 @@ signals: void SampleRateChanged(int sr); + void ConnectedWaveformChanged(); + public slots: void VerifyLength(); @@ -224,6 +237,9 @@ private: TimelinePoints *timeline_points_; + bool autocache_input_video_; + bool autocache_input_audio_; + }; } diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 4a5e5f0a5..d38b610c0 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -63,6 +63,8 @@ Footage::Footage(const QString &filename) : check_timer->setInterval(5000); connect(check_timer, &QTimer::timeout, this, &Footage::CheckFootage); check_timer->start(); + + connect(this->waveform_cache(), &AudioWaveformCache::Validated, this, &ViewerOutput::ConnectedWaveformChanged); } void Footage::Retranslate() diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 00e5dc25b..a78e11b5b 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -552,6 +552,8 @@ void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, Q node->video_frame_cache()->SetUuid(reader->readElementText()); } else if (reader->name() == QStringLiteral("thumb")) { node->thumbnail_cache()->SetUuid(reader->readElementText()); + } else if (reader->name() == QStringLiteral("waveform")) { + node->waveform_cache()->SetUuid(reader->readElementText()); } else { reader->skipCurrentElement(); } @@ -616,6 +618,7 @@ void ProjectSerializer220403::SaveNode(Node *node, QXmlStreamWriter *writer) con writer->writeTextElement(QStringLiteral("audio"), node->audio_playback_cache()->GetUuid().toString()); writer->writeTextElement(QStringLiteral("video"), node->video_frame_cache()->GetUuid().toString()); writer->writeTextElement(QStringLiteral("thumb"), node->thumbnail_cache()->GetUuid().toString()); + writer->writeTextElement(QStringLiteral("waveform"), node->waveform_cache()->GetUuid().toString()); writer->writeEndElement(); // caches diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 8a37e1776..b19e98d0c 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -24,6 +24,8 @@ set(OLIVE_SOURCES render/audioparams.h render/audioplaybackcache.cpp render/audioplaybackcache.h + render/audiowaveformcache.cpp + render/audiowaveformcache.h render/color.cpp render/color.h render/colorprocessor.cpp diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index 55dba2268..6113a6fd9 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -48,9 +48,6 @@ void AudioPlaybackCache::SetParameters(const AudioParams ¶ms) } params_ = params; - visual_.set_channel_count(params_.channel_count()); - - emit ParametersChanged(); } void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, const SampleBuffer &samples) @@ -62,23 +59,6 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &v } } -void AudioPlaybackCache::WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform) -{ - // Write each valid range to the segments - foreach (const TimeRange& r, valid_ranges) { - // Write visual - if (waveform) { - visual_.OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length()); - } else { - visual_.OverwriteSilence(r.in(), r.length()); - } - } - - if (!valid_ranges.isEmpty()) { - emit WaveformUpdated(); - } -} - void AudioPlaybackCache::WriteSilence(const TimeRange &range) { // WritePCM will automatically fill non-existent bytes with silence, so we just have to send diff --git a/app/render/audioplaybackcache.h b/app/render/audioplaybackcache.h index 94f556037..6a7949482 100644 --- a/app/render/audioplaybackcache.h +++ b/app/render/audioplaybackcache.h @@ -68,18 +68,8 @@ public: void WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, const SampleBuffer &samples); - void WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform); - void WriteSilence(const TimeRange &range); - const AudioVisualWaveform &visual() const { return visual_; } - void set_visual(const AudioVisualWaveform &v) { visual_ = v; } - -signals: - void ParametersChanged(); - - void WaveformUpdated(); - private: bool WritePartOfSampleBuffer(const SampleBuffer &samples, const rational &write_start, const rational &buffer_start, const rational &length); @@ -89,8 +79,6 @@ private: AudioParams params_; - AudioVisualWaveform visual_; - }; } diff --git a/app/render/audiowaveformcache.cpp b/app/render/audiowaveformcache.cpp new file mode 100644 index 000000000..ba3a0f506 --- /dev/null +++ b/app/render/audiowaveformcache.cpp @@ -0,0 +1,101 @@ +/*** + + 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 "audiowaveformcache.h" + +namespace olive { + +AudioWaveformCache::AudioWaveformCache(QObject *parent) : + PlaybackCache{parent} +{ +} + +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) { + // Write visual + TimeRangeList::util_remove(&waveforms_, r); + + if (waveform) { + TimeRangeWithWaveform wv = r; + rational local_start = r.in() - range.in(); + wv.waveform = waveform->Mid(local_start, r.length()); + waveforms_.append(wv); + } + + Validate(r); + } +} + +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); + + 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); + } + } +} + +AudioVisualWaveform::Sample AudioWaveformCache::GetSummaryFromTime(const rational &start, const rational &length) const +{ + 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.append(it.value()); + } + + return result; +} + +rational AudioWaveformCache::length() const +{ + rational len = 0; + + foreach (const TimeRangeWithWaveform &wv, waveforms_) { + len = std::max(len, wv.out()); + } + + return len; +} + +} diff --git a/app/render/audiowaveformcache.h b/app/render/audiowaveformcache.h new file mode 100644 index 000000000..0a27eb175 --- /dev/null +++ b/app/render/audiowaveformcache.h @@ -0,0 +1,85 @@ +/*** + + 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 AUDIOWAVEFORMCACHE_H +#define AUDIOWAVEFORMCACHE_H + +#include "audio/audiovisualwaveform.h" +#include "playbackcache.h" + +namespace olive { + +class AudioWaveformCache : public PlaybackCache +{ + Q_OBJECT +public: + AudioWaveformCache(QObject *parent = nullptr); + + void WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform); + + const AudioParams &GetParameters() const { return params_; } + void SetParameters(const AudioParams &p) { params_ = p; } + + void Draw(QPainter* painter, const QRect &rect, const double &scale, const rational &start_time) const; + + AudioVisualWaveform::Sample GetSummaryFromTime(const rational &start, const rational &length) const; + + rational length() const; + +private: + class TimeRangeWithWaveform : public TimeRange + { + public: + TimeRangeWithWaveform() = default; + TimeRangeWithWaveform(const TimeRange &r) : + TimeRange(r) + { + } + + 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_; + + AudioParams params_; + +}; + +} + +#endif // AUDIOWAVEFORMCACHE_H diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index ef48d6284..f395fa908 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -80,7 +80,7 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, RenderTicke RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, RenderTicketPriority priority) { - return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, PlaybackCache::kCacheOnly, priority); + return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, PlaybackCache::kCacheOnly, priority, nullptr); } void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range) @@ -127,14 +127,17 @@ void PreviewAutoCacher::AudioRendered() SampleBuffer buf = watcher->Get().value(); node->audio_playback_cache()->SetParameters(buf.audio_params()); + node->waveform_cache()->SetParameters(buf.audio_params()); PlaybackCache::RequestType type = PlaybackCache::RequestType(watcher->property("type").toInt()); if (type == PlaybackCache::kCacheOnly) { // WritePCM is tolerant to its buffer being null, it will just write silence instead - node->audio_playback_cache()->WritePCM(range, - valid_ranges, - watcher->Get().value()); + if (AudioPlaybackCache *cache = Node::ValueToPtr(watcher->property("cache"))) { + cache->WritePCM(range, + valid_ranges, + watcher->Get().value()); + } } else { // Detect if this audio was incomplete because it was waiting on a conform to finish if (watcher->GetTicket()->property("incomplete").toBool()) { @@ -146,7 +149,9 @@ void PreviewAutoCacher::AudioRendered() d.needing_conform.insert(range); } } else { - node->audio_playback_cache()->WriteWaveform(range, valid_ranges, &waveform); + if (AudioWaveformCache *cache = Node::ValueToPtr(watcher->property("cache"))) { + cache->WriteWaveform(range, valid_ranges, &waveform); + } } } } @@ -320,16 +325,6 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) void PreviewAutoCacher::ConnectToNodeCache(Node *node) { - connect(node->video_frame_cache(), - &PlaybackCache::AutomaticChanged, - this, - &PreviewAutoCacher::VideoAutoCacheEnableChanged); - - connect(node->audio_playback_cache(), - &PlaybackCache::AutomaticChanged, - this, - &PreviewAutoCacher::AudioAutoCacheEnableChanged); - connect(node->video_frame_cache(), &PlaybackCache::Request, this, @@ -345,32 +340,18 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node) this, &PreviewAutoCacher::AudioInvalidatedFromCache); + connect(node->waveform_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::AudioInvalidatedFromCache); + node->video_frame_cache()->LoadState(); node->audio_playback_cache()->LoadState(); node->thumbnail_cache()->LoadState(); - - // Copy invalidated ranges and start rendering if necessary - if (node->video_frame_cache()->IsAutomatic()) { - VideoAutoCacheEnableChangedFromNode(node, true); - } - - if (node->audio_playback_cache()->IsAutomatic()) { - AudioAutoCacheEnableChangedFromNode(node, true); - } } void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) { - disconnect(node->video_frame_cache(), - &PlaybackCache::AutomaticChanged, - this, - &PreviewAutoCacher::VideoAutoCacheEnableChanged); - - disconnect(node->audio_playback_cache(), - &PlaybackCache::AutomaticChanged, - this, - &PreviewAutoCacher::AudioAutoCacheEnableChanged); - disconnect(node->video_frame_cache(), &PlaybackCache::Request, this, @@ -386,17 +367,14 @@ void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) this, &PreviewAutoCacher::AudioInvalidatedFromCache); + disconnect(node->waveform_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::AudioInvalidatedFromCache); + node->video_frame_cache()->SaveState(); node->audio_playback_cache()->SaveState(); node->thumbnail_cache()->SaveState(); - - if (node->video_frame_cache()->IsAutomatic()) { - VideoAutoCacheEnableChangedFromNode(node, false); - } - - if (node->audio_playback_cache()->IsAutomatic()) { - AudioAutoCacheEnableChangedFromNode(node, false); - } } void PreviewAutoCacher::UpdateGraphChangeValue() @@ -474,24 +452,6 @@ void PreviewAutoCacher::AudioInvalidatedFromNode(Node *node, const TimeRange &ra StartCachingAudioRange(node, range, type); } -void PreviewAutoCacher::VideoAutoCacheEnableChangedFromNode(Node *node, bool e) -{ - if (e) { - VideoInvalidatedList(node, node->video_frame_cache()->GetInvalidatedRanges(node->GetVideoCacheRange())); - } else { - CancelVideoTasks(node); - } -} - -void PreviewAutoCacher::AudioAutoCacheEnableChangedFromNode(Node *node, bool e) -{ - if (e) { - AudioInvalidatedList(node, node->audio_playback_cache()->GetInvalidatedRanges(node->GetAudioCacheRange())); - } else { - CancelAudioTasks(node); - } -} - void PreviewAutoCacher::SetPlayhead(const rational &playhead) { cache_range_ = TimeRange(playhead - OLIVE_CONFIG("DiskCacheBehind").value(), @@ -657,7 +617,13 @@ void PreviewAutoCacher::TryRender() // Start job if (Node *copy = copy_map_.value(d.node)) { - RenderAudio(copy, d.range, d.type, RenderTicketPriority::kNormal); + PlaybackCache *cache; + if (d.type == PlaybackCache::kPreviewsOnly) { + cache = d.node->waveform_cache(); + } else { + cache = d.node->audio_playback_cache(); + } + RenderAudio(copy, d.range, d.type, RenderTicketPriority::kNormal, cache); } else { qCritical() << "Failed to find node copy for audio job"; } @@ -705,12 +671,13 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& return watcher; } -RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, PlaybackCache::RequestType type, RenderTicketPriority priority) +RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, PlaybackCache::RequestType type, RenderTicketPriority priority, PlaybackCache *cache) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); watcher->setProperty("node", Node::PtrToValue(node)); watcher->setProperty("type", type); + watcher->setProperty("cache", Node::PtrToValue(cache)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); audio_tasks_.insert(watcher, r); @@ -720,6 +687,7 @@ RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, P rap.generate_waveforms = (type == PlaybackCache::kPreviewsOnly); rap.priority = priority; + rap.clamp = false; RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(rap); watcher->SetTicket(ticket); @@ -744,20 +712,6 @@ void PreviewAutoCacher::ConformFinished() } } -void PreviewAutoCacher::VideoAutoCacheEnableChanged(bool e) -{ - FrameHashCache *cache = static_cast(sender()); - - VideoAutoCacheEnableChangedFromNode(cache->parent(), e); -} - -void PreviewAutoCacher::AudioAutoCacheEnableChanged(bool e) -{ - AudioPlaybackCache *cache = static_cast(sender()); - - AudioAutoCacheEnableChangedFromNode(cache->parent(), e); -} - void PreviewAutoCacher::CacheProxyTaskCancelled() { pending_video_jobs_.clear(); diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index f987dfe46..1256d8106 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -105,7 +105,7 @@ private: RenderTicketWatcher *RenderFrame(Node *node, const rational &time, PlaybackCache::RequestType type, RenderTicketPriority priority, FrameHashCache *cache); - RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, PlaybackCache::RequestType type, RenderTicketPriority priority); + RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, PlaybackCache::RequestType type, RenderTicketPriority priority, PlaybackCache *cache); /** * @brief Process all changes to internal NodeGraph copy @@ -142,9 +142,6 @@ private: void VideoInvalidatedFromNode(Node *node, const olive::TimeRange &range, PlaybackCache::RequestType type); void AudioInvalidatedFromNode(Node *node, const olive::TimeRange &range, PlaybackCache::RequestType type); - void VideoAutoCacheEnableChangedFromNode(Node *node, bool e); - void AudioAutoCacheEnableChangedFromNode(Node *node, bool e); - class QueuedJob { public: enum Type { @@ -264,10 +261,6 @@ private slots: void ConformFinished(); - void VideoAutoCacheEnableChanged(bool e); - - void AudioAutoCacheEnableChanged(bool e); - void CacheProxyTaskCancelled(); }; diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 09d20df00..b86cc64ea 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -112,6 +112,7 @@ RenderTicketPtr RenderManager::RenderAudio(const RenderAudioParams ¶ms) ticket->setProperty("time", QVariant::fromValue(params.range)); ticket->setProperty("type", kTypeAudio); ticket->setProperty("enablewaveforms", params.generate_waveforms); + ticket->setProperty("clamp", params.clamp); ticket->setProperty("aparam", QVariant::fromValue(params.audio_params)); AddTicket(ticket, params.priority); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 6ef2a503a..6bbe3ba17 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -129,6 +129,7 @@ public: audio_params = aparam; generate_waveforms = false; priority = RenderTicketPriority::kNormal; + clamp = true; } Node *node; @@ -136,6 +137,7 @@ public: AudioParams audio_params; bool generate_waveforms; RenderTicketPriority priority; + bool clamp; }; /** diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index f2c920422..f774b07af 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -216,7 +216,9 @@ void RenderProcessor::Run() SampleBuffer samples = sample_val.toSamples(); if (samples.is_allocated()) { - samples.clamp(); + if (ticket_->property("clamp").toBool()) { + samples.clamp(); + } if (ticket_->property("enablewaveforms").toBool()) { AudioVisualWaveform vis; diff --git a/app/widget/audiomonitor/audiomonitor.cpp b/app/widget/audiomonitor/audiomonitor.cpp index 8e7c945f5..cfc2ae781 100644 --- a/app/widget/audiomonitor/audiomonitor.cpp +++ b/app/widget/audiomonitor/audiomonitor.cpp @@ -93,11 +93,12 @@ void AudioMonitor::PushSampleBuffer(const SampleBuffer &d) SetUpdateLoop(true); } -void AudioMonitor::StartWaveform(const AudioVisualWaveform *waveform, const rational &start, int playback_speed) +void AudioMonitor::StartWaveform(const AudioWaveformCache *waveform, const rational &start, int playback_speed) { Stop(); - if (start >= waveform->length()) { + waveform_length_ = waveform->length(); + if (start >= waveform_length_) { return; } @@ -239,7 +240,7 @@ void AudioMonitor::paintGL() if (waveform_) { UpdateValuesFromWaveform(v, delta_time); - if (waveform_time_ >= waveform_->length()) { + if (waveform_time_ >= waveform_length_) { Stop(); } } diff --git a/app/widget/audiomonitor/audiomonitor.h b/app/widget/audiomonitor/audiomonitor.h index d29b74491..cf5d2e8f0 100644 --- a/app/widget/audiomonitor/audiomonitor.h +++ b/app/widget/audiomonitor/audiomonitor.h @@ -28,7 +28,7 @@ #include "audio/audiovisualwaveform.h" #include "common/define.h" #include "render/audioparams.h" -#include "render/audioplaybackcache.h" +#include "render/audiowaveformcache.h" namespace olive { @@ -45,7 +45,7 @@ public: return waveform_; } - static void StartWaveformOnAll(const AudioVisualWaveform *waveform, const rational& start, int playback_speed) + static void StartWaveformOnAll(const AudioWaveformCache *waveform, const rational& start, int playback_speed) { foreach (AudioMonitor *m, instances_) { m->StartWaveform(waveform, start, playback_speed); @@ -73,7 +73,7 @@ public slots: void PushSampleBuffer(const SampleBuffer &samples); - void StartWaveform(const AudioVisualWaveform *waveform, const rational& start, int playback_speed); + void StartWaveform(const AudioWaveformCache *waveform, const rational& start, int playback_speed); protected: virtual void paintGL() override; @@ -97,8 +97,9 @@ private: qint64 last_time_; - const AudioVisualWaveform* waveform_; + const AudioWaveformCache* waveform_; rational waveform_time_; + rational waveform_length_; int playback_speed_; diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index d0e066493..a05311395 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -699,7 +699,8 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) // Place the copy instead of the original block block = static_cast(Node::CopyNodeInGraph(block, command)); if (ClipBlock *new_clip = dynamic_cast(block)) { - new_clip->set_waveform(static_cast(p.block)->waveform()); + qDebug() << "FIXME: Copy clip stub"; Q_UNUSED(new_clip) + //new_clip->set_waveform(static_cast(p.block)->waveform()); } } diff --git a/app/widget/timelinewidget/undo/timelineundosplit.cpp b/app/widget/timelinewidget/undo/timelineundosplit.cpp index 74626750c..3900d2b7b 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.cpp +++ b/app/widget/timelinewidget/undo/timelineundosplit.cpp @@ -44,7 +44,10 @@ void BlockSplitCommand::redo() if (ClipBlock *new_clip = dynamic_cast(new_block_)) { ClipBlock *old_clip = static_cast(block_); - new_clip->set_waveform(old_clip->waveform()); + qDebug() << "FIXME: Copy waveform stub"; + Q_UNUSED(old_clip) + Q_UNUSED(new_clip) + //new_clip->set_waveform(old_clip->waveform()); } // Determine our new lengths diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index a11a449ba..7c105e2a8 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -558,11 +558,11 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q // Draw waveform if (clip->GetTrackType() == Track::kAudio && show_waveforms_) { - if (const AudioVisualWaveform *wave = clip->waveform()) { + if (const AudioWaveformCache *wave = clip->waveform()) { rational waveform_start = SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in; painter->setPen(shadow_color); - AudioVisualWaveform::DrawWaveform(painter, preview_rect, this->GetScale(), *wave, waveform_start); + wave->Draw(painter, preview_rect, this->GetScale(), waveform_start); } } diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index 92dc7e500..8092cb224 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -47,13 +47,13 @@ AudioWaveformView::AudioWaveformView(QWidget *parent) : setAlignment(Qt::AlignLeft | Qt::AlignTop); } -void AudioWaveformView::SetViewer(AudioPlaybackCache *playback) +void AudioWaveformView::SetViewer(ViewerOutput *playback) { if (playback_) { pool_.clear(); pool_.waitForDone(); - disconnect(playback_, &AudioPlaybackCache::Validated, this, static_cast(&AudioWaveformView::update)); + disconnect(playback_, &ViewerOutput::ConnectedWaveformChanged, this, static_cast(&AudioWaveformView::update)); SetTimebase(0); } @@ -61,9 +61,9 @@ void AudioWaveformView::SetViewer(AudioPlaybackCache *playback) playback_ = playback; if (playback_) { - connect(playback_, &AudioPlaybackCache::Validated, this, static_cast(&AudioWaveformView::update)); + connect(playback_, &ViewerOutput::ConnectedWaveformChanged, this, static_cast(&AudioWaveformView::update)); - SetTimebase(playback_->GetParameters().sample_rate_as_time_base()); + SetTimebase(playback_->GetAudioParams().sample_rate_as_time_base()); } } @@ -75,8 +75,12 @@ void AudioWaveformView::drawForeground(QPainter *p, const QRectF &rect) return; } - const AudioParams& params = playback_->GetParameters(); + const AudioWaveformCache *wave = playback_->GetConnectedWaveform(); + if (!wave) { + return; + } + const AudioParams& params = wave->GetParameters(); if (!params.is_valid()) { return; } @@ -86,7 +90,7 @@ void AudioWaveformView::drawForeground(QPainter *p, const QRectF &rect) // Draw waveform p->setPen(QColor(64, 255, 160)); // FIXME: Hardcoded color - AudioVisualWaveform::DrawWaveform(p, rect.toRect(), GetScale(), playback_->visual(), SceneToTime(GetScroll())); + wave->Draw(p, rect.toRect(), GetScale(), SceneToTime(GetScroll())); // Draw playhead p->setPen(PLAYHEAD_COLOR); diff --git a/app/widget/viewer/audiowaveformview.h b/app/widget/viewer/audiowaveformview.h index 970cb07ad..73162a06a 100644 --- a/app/widget/viewer/audiowaveformview.h +++ b/app/widget/viewer/audiowaveformview.h @@ -36,7 +36,7 @@ class AudioWaveformView : public SeekableWidget public: AudioWaveformView(QWidget* parent = nullptr); - void SetViewer(AudioPlaybackCache *playback); + void SetViewer(ViewerOutput *playback); protected: virtual void drawForeground(QPainter *painter, const QRectF &rect) override; @@ -44,7 +44,7 @@ protected: private: QThreadPool pool_; - AudioPlaybackCache *playback_; + ViewerOutput *playback_; }; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index d0ad7d6e0..371f1e89e 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -232,7 +232,7 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) UpdateStack(); - waveform_view_->SetViewer(GetConnectedNode()->audio_playback_cache()); + waveform_view_->SetViewer(GetConnectedNode()); waveform_view_->ConnectTimelinePoints(GetConnectedNode()->GetTimelinePoints()); UpdateRendererVideoParameters(); @@ -947,7 +947,7 @@ void ViewerWidget::FinishPlayPreprocess() } prequeued_audio_.clear(); - AudioMonitor::StartWaveformOnAll(&GetConnectedNode()->audio_playback_cache()->visual(), + AudioMonitor::StartWaveformOnAll(GetConnectedNode()->GetConnectedWaveform(), GetTime(), playback_speed_); } From e3c3ee9ac472298f0f4cd5e59cd352460b484ba4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 30 May 2022 08:59:25 -0700 Subject: [PATCH 11/53] code: removed unnecessary code --- app/audio/audiovisualwaveform.cpp | 1 - app/codec/ffmpeg/ffmpegdecoder.cpp | 1 - app/common/CMakeLists.txt | 3 - app/common/flipmodifiers.cpp | 41 -------------- app/common/flipmodifiers.h | 34 ------------ app/common/functiontimer.h | 55 ------------------- app/common/qtutils.cpp | 17 ++++++ app/common/qtutils.h | 2 + app/node/generator/text/textv2.cpp | 1 - app/node/generator/text/textv3.cpp | 1 - app/widget/nodeparamview/nodeparamview.cpp | 1 - app/widget/nodeview/nodeviewitem.cpp | 7 +-- app/widget/nodeview/nodeviewscene.cpp | 1 - app/widget/timelinewidget/tool/pointer.cpp | 1 - .../timelinewidget/view/timelineview.cpp | 1 - app/widget/viewer/viewerdisplay.cpp | 1 - 16 files changed, 22 insertions(+), 146 deletions(-) delete mode 100644 app/common/flipmodifiers.cpp delete mode 100644 app/common/flipmodifiers.h delete mode 100644 app/common/functiontimer.h diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index c9b2ef50e..58aabc788 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -25,7 +25,6 @@ #include "config/config.h" #include "common/cpuoptimize.h" -#include "common/functiontimer.h" namespace olive { diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 29af60cd6..10d79b2bc 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -43,7 +43,6 @@ extern "C" { #include "common/define.h" #include "common/ffmpegutils.h" #include "common/filefunctions.h" -#include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "render/framehashcache.h" #include "render/diskmanager.h" diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index b4bdea348..88d4e5d12 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -34,9 +34,6 @@ set(OLIVE_SOURCES common/ffmpegutils.h common/filefunctions.cpp common/filefunctions.h - common/flipmodifiers.cpp - common/flipmodifiers.h - common/functiontimer.h common/html.cpp common/html.h common/jobtime.cpp diff --git a/app/common/flipmodifiers.cpp b/app/common/flipmodifiers.cpp deleted file mode 100644 index ad48704c7..000000000 --- a/app/common/flipmodifiers.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/*** - - 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 "flipmodifiers.h" - -namespace olive { - -Qt::KeyboardModifiers FlipControlAndShiftModifiers(Qt::KeyboardModifiers e) { - if (e & Qt::ControlModifier & Qt::ShiftModifier) { - return e; - } - - if (e & Qt::ShiftModifier) { - e |= Qt::ControlModifier; - e &= ~Qt::ShiftModifier; - } else if (e & Qt::ControlModifier) { - e |= Qt::ShiftModifier; - e &= ~Qt::ControlModifier; - } - - return e; -} - -} diff --git a/app/common/flipmodifiers.h b/app/common/flipmodifiers.h deleted file mode 100644 index 12b408e39..000000000 --- a/app/common/flipmodifiers.h +++ /dev/null @@ -1,34 +0,0 @@ -/*** - - 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 FLIPMODIFIERS_H -#define FLIPMODIFIERS_H - -#include - -#include "common/define.h" - -namespace olive { - -Qt::KeyboardModifiers FlipControlAndShiftModifiers(Qt::KeyboardModifiers e); - -} - -#endif // FLIPMODIFIERS_H diff --git a/app/common/functiontimer.h b/app/common/functiontimer.h deleted file mode 100644 index ba1aadbdc..000000000 --- a/app/common/functiontimer.h +++ /dev/null @@ -1,55 +0,0 @@ -/*** - - 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 FUNCTIONTIMER_H -#define FUNCTIONTIMER_H - -#include -#include - -#define TIME_THIS_FUNCTION FunctionTimer __f(__FUNCTION__) -#define START_TIMING {FunctionTimer *__f = new FunctionTimer(__FUNCTION__) -#define STOP_TIMING delete __f;}void() - -class FunctionTimer { -public: - FunctionTimer(const char* s) - { - name_ = s; - time_ = QDateTime::currentMSecsSinceEpoch(); - } - - ~FunctionTimer() - { - qint64 elapsed = (QDateTime::currentMSecsSinceEpoch() - time_); - - if (elapsed > 1) { - qDebug() << name_ << "took" << elapsed; - } - } - -private: - const char* name_; - - qint64 time_; - -}; - -#endif // FUNCTIONTIMER_H diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index e4cc56c28..cfcaeac08 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -115,4 +115,21 @@ QStringList QtUtils::WordWrapString(const QString &s, const QFontMetrics &fm, in return list; } +Qt::KeyboardModifiers QtUtils::FlipControlAndShiftModifiers(Qt::KeyboardModifiers e) +{ + if (e & Qt::ControlModifier & Qt::ShiftModifier) { + return e; + } + + if (e & Qt::ShiftModifier) { + e |= Qt::ControlModifier; + e &= ~Qt::ShiftModifier; + } else if (e & Qt::ControlModifier) { + e |= Qt::ShiftModifier; + e &= ~Qt::ControlModifier; + } + + return e; +} + } diff --git a/app/common/qtutils.h b/app/common/qtutils.h index 122a5022c..0d1f5adb3 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -64,6 +64,8 @@ public: static QStringList WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width); + static Qt::KeyboardModifiers FlipControlAndShiftModifiers(Qt::KeyboardModifiers e); + }; } diff --git a/app/node/generator/text/textv2.cpp b/app/node/generator/text/textv2.cpp index 0df750fc6..7f1bdd6d0 100644 --- a/app/node/generator/text/textv2.cpp +++ b/app/node/generator/text/textv2.cpp @@ -25,7 +25,6 @@ #include #include "common/cpuoptimize.h" -#include "common/functiontimer.h" namespace olive { diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp index da6f9bfe8..f3f9cdecf 100644 --- a/app/node/generator/text/textv3.cpp +++ b/app/node/generator/text/textv3.cpp @@ -24,7 +24,6 @@ #include #include -#include "common/functiontimer.h" #include "common/html.h" #include "node/project/project.h" diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 99a4cd95f..10b944a43 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -26,7 +26,6 @@ #include #include -#include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "node/output/viewer/viewer.h" #include "node/project/serializer/serializer.h" diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 9a04ee961..5fcac4548 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -26,7 +26,6 @@ #include #include -#include "common/flipmodifiers.h" #include "common/qtutils.h" #include "config/config.h" #include "core.h" @@ -434,7 +433,7 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) return; } - event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); + event->setModifiers(QtUtils::FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mousePressEvent(event); } @@ -445,7 +444,7 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) return; } - event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); + event->setModifiers(QtUtils::FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mouseMoveEvent(event); } @@ -457,7 +456,7 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) return; } - event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); + event->setModifiers(QtUtils::FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mouseReleaseEvent(event); } diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 833a83d99..f2ae527e2 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -20,7 +20,6 @@ #include "nodeviewscene.h" -#include "common/functiontimer.h" #include "core.h" #include "node/project/sequence/sequence.h" #include "nodeviewedge.h" diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index a05311395..71f57b63c 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -24,7 +24,6 @@ #include #include "common/clamp.h" -#include "common/flipmodifiers.h" #include "common/qtutils.h" #include "common/range.h" #include "common/timecodefunctions.h" diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 7c105e2a8..7adf8f38d 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -28,7 +28,6 @@ #include #include "config/config.h" -#include "common/flipmodifiers.h" #include "common/qtutils.h" #include "common/timecodefunctions.h" #include "node/project/footage/footage.h" diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index cdbe6fdc6..18bbcd1c0 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -35,7 +35,6 @@ #include #include "common/define.h" -#include "common/functiontimer.h" #include "common/html.h" #include "common/qtutils.h" #include "config/config.h" From e3830116b012061c92f8a0e96549ee76db73a503 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 30 May 2022 08:59:56 -0700 Subject: [PATCH 12/53] clip: implement auto-cache option --- app/node/block/clip/clip.cpp | 98 ++++++++++++-------- app/node/block/clip/clip.h | 9 +- app/widget/timelinewidget/timelinewidget.cpp | 19 ++++ app/widget/timelinewidget/timelinewidget.h | 2 + 4 files changed, 88 insertions(+), 40 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 6a61b73ac..43aff7934 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -34,12 +34,12 @@ const QString ClipBlock::kMediaInInput = QStringLiteral("media_in_in"); const QString ClipBlock::kSpeedInput = QStringLiteral("speed_in"); const QString ClipBlock::kReverseInput = QStringLiteral("reverse_in"); const QString ClipBlock::kMaintainAudioPitchInput = QStringLiteral("maintain_audio_pitch_in"); +const QString ClipBlock::kAutoCacheInput = QStringLiteral("autocache_in"); ClipBlock::ClipBlock() : in_transition_(nullptr), out_transition_(nullptr), - connected_viewer_(nullptr), - autocache_(false) + connected_viewer_(nullptr) { AddInput(kMediaInInput, NodeValue::kRational, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); SetInputProperty(kMediaInInput, QStringLiteral("view"), RationalSlider::kTime); @@ -53,6 +53,8 @@ ClipBlock::ClipBlock() : AddInput(kMaintainAudioPitchInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kAutoCacheInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + PrependInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); //SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer)); @@ -122,6 +124,17 @@ void ClipBlock::set_media_in(const rational &media_in) SetStandardValue(kMediaInInput, QVariant::fromValue(media_in)); } +void ClipBlock::SetAutocache(bool e) +{ + SetStandardValue(kAutoCacheInput, e); + + if (e) { + RequestInvalidatedFromConnected(); + } else { + qDebug() << "FIXME: signal that frames for this clip should be unqueued"; + } +} + rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool ignore_reverse, bool ignore_speed) const { // These constants are not considered "values" per se, so we don't modify them @@ -176,30 +189,56 @@ rational ClipBlock::MediaToSequenceTime(const rational &media_time) const return sequence_time; } +void ClipBlock::RequestInvalidatedFromConnected() +{ + Track::Type type = GetTrackType(); + + if (type == Track::kVideo || type == Track::kAudio) { + if (Node *connected = GetConnectedOutput(kBufferIn)) { + TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); + if (type == Track::kVideo) { + // Handle thumbnails + { + TimeRangeList invalid = connected->thumbnail_cache()->GetInvalidatedRanges(max_range); + for (const TimeRange &r : invalid) { + emit connected->thumbnail_cache()->Request(r, PlaybackCache::kPreviewsOnly); + } + } + + // Handle video cache + if (IsAutocaching()) { + TimeRangeList invalid = connected->video_frame_cache()->GetInvalidatedRanges(max_range); + for (const TimeRange &r : invalid) { + emit connected->video_frame_cache()->Request(r, PlaybackCache::kPreviewsOnly); + } + } + } else if (type == Track::kAudio) { + { + TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); + for (const TimeRange &r : invalid) { + emit connected->waveform_cache()->Request(r, PlaybackCache::kPreviewsOnly); + } + } + + if (IsAutocaching()) { + TimeRangeList invalid = connected->audio_playback_cache()->GetInvalidatedRanges(max_range); + for (const TimeRange &r : invalid) { + emit connected->audio_playback_cache()->Request(r, PlaybackCache::kPreviewsOnly); + } + } + } + } + } +} + void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) { Q_UNUSED(element) // If signal is from texture input, transform all times from media time to sequence time if (from == kBufferIn) { - Track::Type type = GetTrackType(); - - if (type == Track::kVideo || type == Track::kAudio) { - if (Node *connected = GetConnectedOutput(from, element)) { - TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, length())); - if (type == Track::kVideo) { - emit connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); - if (autocache_) { - emit connected->video_frame_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); - } - } else if (type == Track::kAudio) { - emit connected->waveform_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); - if (autocache_) { - emit connected->audio_playback_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); - } - } - } - } + // Render caches where necessary + RequestInvalidatedFromConnected(); // Adjust range from media time to sequence time TimeRange adj; @@ -320,24 +359,7 @@ void ClipBlock::Retranslate() void ClipBlock::ConnectedToPreviewEvent() { - Track::Type type = GetTrackType(); - - if (type == Track::kVideo || type == Track::kAudio) { - if (Node *connected = GetConnectedOutput(kBufferIn)) { - TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); - if (type == Track::kVideo) { - TimeRangeList invalid = connected->thumbnail_cache()->GetInvalidatedRanges(max_range); - for (const TimeRange &r : invalid) { - emit connected->thumbnail_cache()->Request(r, PlaybackCache::kPreviewsOnly); - } - } else if (type == Track::kAudio) { - TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); - for (const TimeRange &r : invalid) { - emit connected->waveform_cache()->Request(r, PlaybackCache::kPreviewsOnly); - } - } - } - } + RequestInvalidatedFromConnected(); } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index b43bfde09..13c5a64ad 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -59,6 +59,9 @@ public: rational media_in() const; void set_media_in(const rational& media_in); + bool IsAutocaching() const { return GetStandardValue(kAutoCacheInput).toBool(); } + void SetAutocache(bool e); + virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override; virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; @@ -166,6 +169,8 @@ public: static const QString kReverseInput; static const QString kMaintainAudioPitchInput; + static const QString kAutoCacheInput; + protected: virtual void LinkChangeEvent() override; @@ -178,6 +183,8 @@ private: rational MediaToSequenceTime(const rational& media_time) const; + void RequestInvalidatedFromConnected(); + QVector block_links_; TransitionBlock* in_transition_; @@ -185,8 +192,6 @@ private: ViewerOutput *connected_viewer_; - bool autocache_; - private: rational last_media_in_; diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 59a61addc..4bb2de0ee 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -54,6 +54,7 @@ #include "undo/timelineundoworkarea.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" +#include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/nodeview/nodeviewundo.h" #include "widget/timeruler/timeruler.h" @@ -1072,6 +1073,11 @@ void TimelineWidget::ShowContextMenu() menu.addSeparator(); if (ClipBlock *clip = dynamic_cast(selected.first())) { + QAction *autocache_action = menu.addAction(tr("Auto-Cache")); + autocache_action->setCheckable(true); + autocache_action->setChecked(clip->IsAutocaching()); + connect(autocache_action, &QAction::triggered, this, &TimelineWidget::SetSelectedClipsAutocaching); + if (clip->connected_viewer()) { QAction *reveal_in_project = menu.addAction(tr("Reveal in Project")); reveal_in_project->setData(reinterpret_cast(clip->connected_viewer())); @@ -1247,6 +1253,19 @@ void TimelineWidget::TrackAboutToBeDeleted(Track *track) } } +void TimelineWidget::SetSelectedClipsAutocaching(bool e) +{ + MultiUndoCommand *command = new MultiUndoCommand(); + + for (Block *b : selected_blocks_) { + if (ClipBlock *clip = dynamic_cast(b)) { + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(clip, ClipBlock::kAutoCacheInput)), e)); + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} + void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost) { ghost_items_.append(ghost); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index c1ec0b292..ff431e792 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -433,6 +433,8 @@ private slots: void TrackAboutToBeDeleted(Track *track); + void SetSelectedClipsAutocaching(bool e); + }; } From 9cc861cdd5cb0c503432a521d9e8327237e5e418 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 30 May 2022 13:40:39 -0700 Subject: [PATCH 13/53] cache: cancel tasks on disable --- app/dialog/sequence/sequence.cpp | 8 +- .../sequence/sequencedialogparametertab.cpp | 2 +- app/node/block/clip/clip.cpp | 71 +++--- app/node/block/clip/clip.h | 6 +- app/node/node.cpp | 2 +- app/node/node.h | 4 +- app/node/output/viewer/viewer.cpp | 8 +- app/node/output/viewer/viewer.h | 3 + app/render/framehashcache.h | 10 + app/render/playbackcache.cpp | 16 +- app/render/playbackcache.h | 14 +- app/render/previewautocacher.cpp | 235 +++++++++--------- app/render/previewautocacher.h | 34 ++- app/widget/viewer/viewer.cpp | 7 +- app/widget/viewer/viewer.h | 2 - 15 files changed, 207 insertions(+), 215 deletions(-) diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index 4743bf4c1..368989332 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -159,7 +159,7 @@ void SequenceDialog::accept() sequence_->SetVideoParams(video_params); sequence_->SetAudioParams(audio_params); sequence_->SetLabel(name_field_->text()); - sequence_->video_frame_cache()->SetIsAutomatic(parameter_tab_->GetSelectedPreviewAutoCache()); + sequence_->SetVideoAutoCacheEnabled(parameter_tab_->GetSelectedPreviewAutoCache()); } QDialog::accept(); @@ -193,7 +193,7 @@ SequenceDialog::SequenceParamCommand::SequenceParamCommand(Sequence* s, old_video_params_(s->GetVideoParams()), old_audio_params_(s->GetAudioParams()), old_name_(s->GetLabel()), - old_autocache_(s->video_frame_cache()->IsAutomatic()) + old_autocache_(s->IsVideoAutoCacheEnabled()) { } @@ -211,7 +211,7 @@ void SequenceDialog::SequenceParamCommand::redo() sequence_->SetAudioParams(new_audio_params_); } sequence_->SetLabel(new_name_); - sequence_->video_frame_cache()->SetIsAutomatic(new_autocache_); + sequence_->SetVideoAutoCacheEnabled(new_autocache_); } void SequenceDialog::SequenceParamCommand::undo() @@ -223,7 +223,7 @@ void SequenceDialog::SequenceParamCommand::undo() sequence_->SetAudioParams(old_audio_params_); } sequence_->SetLabel(old_name_); - sequence_->video_frame_cache()->SetIsAutomatic(old_autocache_); + sequence_->SetVideoAutoCacheEnabled(old_autocache_); } } diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp index b00980d1c..1d0fa868e 100644 --- a/app/dialog/sequence/sequencedialogparametertab.cpp +++ b/app/dialog/sequence/sequencedialogparametertab.cpp @@ -89,7 +89,7 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg interlacing_combo_->SetInterlaceMode(vp.interlacing()); preview_resolution_field_->SetDivider(vp.divider()); preview_format_field_->SetPixelFormat(vp.format()); - preview_autocache_field_->setChecked(sequence->video_frame_cache()->IsAutomatic()); + preview_autocache_field_->setChecked(sequence->IsVideoAutoCacheEnabled()); audio_sample_rate_field_->SetSampleRate(ap.sample_rate()); audio_channels_field_->SetChannelLayout(ap.channel_layout()); diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 43aff7934..018984bce 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -127,12 +127,6 @@ void ClipBlock::set_media_in(const rational &media_in) void ClipBlock::SetAutocache(bool e) { SetStandardValue(kAutoCacheInput, e); - - if (e) { - RequestInvalidatedFromConnected(); - } else { - qDebug() << "FIXME: signal that frames for this clip should be unqueued"; - } } rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, bool ignore_reverse, bool ignore_speed) const @@ -189,7 +183,7 @@ rational ClipBlock::MediaToSequenceTime(const rational &media_time) const return sequence_time; } -void ClipBlock::RequestInvalidatedFromConnected() +void ClipBlock::RequestInvalidatedFromConnected(const TimeRange &range) { Track::Type type = GetTrackType(); @@ -198,39 +192,39 @@ void ClipBlock::RequestInvalidatedFromConnected() TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); if (type == Track::kVideo) { // Handle thumbnails - { - TimeRangeList invalid = connected->thumbnail_cache()->GetInvalidatedRanges(max_range); - for (const TimeRange &r : invalid) { - emit connected->thumbnail_cache()->Request(r, PlaybackCache::kPreviewsOnly); - } - } + RequestInvalidatedForCache(connected->thumbnail_cache(), max_range, range); // Handle video cache if (IsAutocaching()) { - TimeRangeList invalid = connected->video_frame_cache()->GetInvalidatedRanges(max_range); - for (const TimeRange &r : invalid) { - emit connected->video_frame_cache()->Request(r, PlaybackCache::kPreviewsOnly); - } + RequestInvalidatedForCache(connected->video_frame_cache(), max_range, range); } } else if (type == Track::kAudio) { - { - TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); - for (const TimeRange &r : invalid) { - emit connected->waveform_cache()->Request(r, PlaybackCache::kPreviewsOnly); - } - } + // Handle waveforms + RequestInvalidatedForCache(connected->waveform_cache(), max_range, range); + // Handle audio cache if (IsAutocaching()) { - TimeRangeList invalid = connected->audio_playback_cache()->GetInvalidatedRanges(max_range); - for (const TimeRange &r : invalid) { - emit connected->audio_playback_cache()->Request(r, PlaybackCache::kPreviewsOnly); - } + RequestInvalidatedForCache(connected->audio_playback_cache(), max_range, range); } } } } } +void ClipBlock::RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range) +{ + if ((range.in() == RATIONAL_MIN && range.out() == RATIONAL_MAX) || !range.length().isNull()) { + // Request only this range + emit cache->Request(range.Intersected(max_range)); + } else { + // Request all ranges currently marked as invalid + TimeRangeList invalid = cache->GetInvalidatedRanges(max_range); + for (const TimeRange &r : invalid) { + emit cache->Request(r); + } + } +} + void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) { Q_UNUSED(element) @@ -238,7 +232,7 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int // If signal is from texture input, transform all times from media time to sequence time if (from == kBufferIn) { // Render caches where necessary - RequestInvalidatedFromConnected(); + RequestInvalidatedFromConnected(range); // Adjust range from media time to sequence time TimeRange adj; @@ -311,6 +305,27 @@ void ClipBlock::InputDisconnectedEvent(const QString &input, int element, Node * } } +void ClipBlock::InputValueChangedEvent(const QString &input, int element) +{ + super::InputValueChangedEvent(input, element); + + if (input == kAutoCacheInput) { + if (IsAutocaching()) { + RequestInvalidatedFromConnected(); + } else { + Track::Type type = GetTrackType(); + + if (Node *connected = GetConnectedOutput(kBufferIn)) { + if (type == Track::kVideo) { + emit connected->video_frame_cache()->CancelAll(); + } else if (type == Track::kAudio) { + emit connected->audio_playback_cache()->CancelAll(); + } + } + } + } +} + TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const { Q_UNUSED(element) diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 13c5a64ad..26e76e5ce 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -178,12 +178,16 @@ protected: virtual void InputDisconnectedEvent(const QString& input, int element, Node *output) override; + virtual void InputValueChangedEvent(const QString& input, int element) override; + private: rational SequenceToMediaTime(const rational& sequence_time, bool ignore_reverse = false, bool ignore_speed = false) const; rational MediaToSequenceTime(const rational& media_time) const; - void RequestInvalidatedFromConnected(); + void RequestInvalidatedFromConnected(const TimeRange &range = TimeRange()); + + void RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range); QVector block_links_; diff --git a/app/node/node.cpp b/app/node/node.cpp index 7d3bc7345..2b5220195 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -54,7 +54,7 @@ Node::Node() : AddInput(kEnabledInput, NodeValue::kBoolean, true); video_cache_ = new FrameHashCache(this); - thumbnail_cache_ = new FrameHashCache(this); + thumbnail_cache_ = new ThumbnailCache(this); audio_cache_ = new AudioPlaybackCache(this); waveform_cache_ = new AudioWaveformCache(this); } diff --git a/app/node/node.h b/app/node/node.h index 6a96f97a1..c331d9b6d 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -227,7 +227,7 @@ public: return video_cache_; } - FrameHashCache* thumbnail_cache() const + ThumbnailCache* thumbnail_cache() const { return thumbnail_cache_; } @@ -1416,7 +1416,7 @@ private: QString effect_input_; FrameHashCache *video_cache_; - FrameHashCache *thumbnail_cache_; + ThumbnailCache *thumbnail_cache_; AudioPlaybackCache *audio_cache_; AudioWaveformCache *waveform_cache_; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 84492de8f..efad82cc7 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -226,13 +226,13 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, //emit 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), PlaybackCache::kPreviewsOnly); + emit 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), PlaybackCache::kPreviewsOnly); + emit connected->waveform_cache()->Request(range.Intersected(max_range)); if (autocache_input_audio_) { - emit connected->audio_playback_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); + emit connected->audio_playback_cache()->Request(range.Intersected(max_range)); } } } @@ -392,7 +392,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, PlaybackCache::kPreviewsOnly); + emit connected->waveform_cache()->Request(r); } } } diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index daca1d4a4..156ec6a3d 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -184,6 +184,9 @@ public: virtual void ConnectedToPreviewEvent() override; + bool IsVideoAutoCacheEnabled() const { qDebug() << "sequence ac is a stub"; return false; } + void SetVideoAutoCacheEnabled(bool e) { qDebug() << "sequence ac is a stub"; } + static const QString kVideoParamsInput; static const QString kAudioParamsInput; static const QString kSubtitleParamsInput; diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index 460d33fee..e6a726dbb 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -91,6 +91,16 @@ private slots: }; +class ThumbnailCache : public FrameHashCache +{ + Q_OBJECT +public: + ThumbnailCache(QObject* parent = nullptr) : + FrameHashCache(parent) + { + } +}; + } #endif // VIDEORENDERFRAMECACHE_H diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 3d5db2258..80384fb63 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -39,10 +39,6 @@ void PlaybackCache::Invalidate(const TimeRange &r) InvalidateEvent(r); emit Invalidated(r); - - if (automatic_) { - emit Request(r, kCacheOnly); - } } Node *PlaybackCache::parent() const @@ -155,21 +151,11 @@ Project *PlaybackCache::GetProject() const } PlaybackCache::PlaybackCache(QObject *parent) : - QObject(parent), - automatic_(false) + QObject(parent) { uuid_ = QUuid::createUuid(); } -void PlaybackCache::SetIsAutomatic(bool e) -{ - if (automatic_ != e) { - automatic_ = e; - - emit AutomaticChanged(automatic_); - } -} - TimeRangeList PlaybackCache::GetInvalidatedRanges(TimeRange intersecting) { TimeRangeList invalidated; diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index e5c7db383..c36edd1b3 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -43,9 +43,6 @@ public: const QUuid &GetUuid() const { return uuid_; } void SetUuid(const QUuid &u) { uuid_ = u; } - bool IsAutomatic() const { return automatic_; } - void SetIsAutomatic(bool e); - TimeRangeList GetInvalidatedRanges(TimeRange intersecting); TimeRangeList GetInvalidatedRanges(const rational &length) { @@ -69,11 +66,6 @@ public: QDir GetThisCacheDirectory() const; static QDir GetThisCacheDirectory(const QString &cache_path, const QUuid &cache_id); - enum RequestType { - kCacheOnly, - kPreviewsOnly - }; - void LoadState(); void SaveState(); @@ -85,9 +77,9 @@ signals: void Validated(const olive::TimeRange& r); - void Request(const olive::TimeRange& r, olive::PlaybackCache::RequestType type); + void Request(const olive::TimeRange& r); - void AutomaticChanged(bool e); + void CancelAll(); protected: void Validate(const TimeRange& r, bool signal = true); @@ -105,8 +97,6 @@ private: QUuid uuid_; - bool automatic_; - }; } diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index f395fa908..1ace48877 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -80,28 +80,44 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, RenderTicke RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, RenderTicketPriority priority) { - return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, PlaybackCache::kCacheOnly, priority, nullptr); + return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, priority, nullptr); } void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range) { - FrameHashCache *cache = static_cast(sender()); + PlaybackCache *cache = static_cast(sender()); - VideoInvalidatedFromNode(cache->parent(), range, PlaybackCache::kCacheOnly); + VideoInvalidatedFromNode(cache, range); } -void PreviewAutoCacher::ThumbnailsInvalidatedFromCache(const TimeRange &range) +void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range) { - FrameHashCache *cache = static_cast(sender()); + PlaybackCache *cache = static_cast(sender()); - VideoInvalidatedFromNode(cache->parent(), range, PlaybackCache::kPreviewsOnly); + AudioInvalidatedFromNode(cache, range); } -void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range, PlaybackCache::RequestType type) +void PreviewAutoCacher::CancelForCache() { - AudioPlaybackCache *cache = static_cast(sender()); + PlaybackCache *cache = static_cast(sender()); - AudioInvalidatedFromNode(cache->parent(), range, type); + if (dynamic_cast(cache) || dynamic_cast(cache)) { + for (auto it=pending_video_jobs_.begin(); it!=pending_video_jobs_.end(); ) { + if ((*it).cache == cache) { + it = pending_video_jobs_.erase(it); + } else { + it++; + } + } + } else if (dynamic_cast(cache) || dynamic_cast(cache)) { + for (auto it=pending_audio_jobs_.begin(); it!=pending_audio_jobs_.end(); ) { + if ((*it).cache == cache) { + it = pending_audio_jobs_.erase(it); + } else { + it++; + } + } + } } void PreviewAutoCacher::AudioRendered() @@ -111,46 +127,45 @@ void PreviewAutoCacher::AudioRendered() // If the task list doesn't contain this watcher, presumably it was cleared as a result of a // viewer switch, so we'll completely ignore this watcher - if (audio_tasks_.contains(watcher)) { + if (running_audio_tasks_.removeOne(watcher)) { // Assume that a "result" is a fully completed image and a non-result is a cancelled ticket - TimeRange range = audio_tasks_.take(watcher); + TimeRange range = watcher->property("time").value(); Node *node = copy_map_.key(Node::ValueToPtr(watcher->property("node"))); if (watcher->HasResult() && node) { - AudioCacheData &d = audio_cache_data_[node]; + if (PlaybackCache *cache = Node::ValueToPtr(watcher->property("cache"))) { + AudioCacheData &d = audio_cache_data_[cache]; - JobTime watcher_job_time = watcher->property("job").value(); + JobTime watcher_job_time = watcher->property("job").value(); - TimeRangeList valid_ranges = d.job_tracker.getCurrentSubRanges(range, watcher_job_time); + TimeRangeList valid_ranges = d.job_tracker.getCurrentSubRanges(range, watcher_job_time); - AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value(); + AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value(); - SampleBuffer buf = watcher->Get().value(); - node->audio_playback_cache()->SetParameters(buf.audio_params()); - node->waveform_cache()->SetParameters(buf.audio_params()); + SampleBuffer buf = watcher->Get().value(); + node->audio_playback_cache()->SetParameters(buf.audio_params()); + node->waveform_cache()->SetParameters(buf.audio_params()); - PlaybackCache::RequestType type = PlaybackCache::RequestType(watcher->property("type").toInt()); + bool incomplete = watcher->GetTicket()->property("incomplete").toBool(); - if (type == PlaybackCache::kCacheOnly) { - // WritePCM is tolerant to its buffer being null, it will just write silence instead - if (AudioPlaybackCache *cache = Node::ValueToPtr(watcher->property("cache"))) { - cache->WritePCM(range, - valid_ranges, - watcher->Get().value()); + if (AudioPlaybackCache *pcm = dynamic_cast(cache)) { + // WritePCM is tolerant to its buffer being null, it will just write silence instead + pcm->WritePCM(range, + valid_ranges, + watcher->Get().value()); + } else if (AudioWaveformCache *wave = dynamic_cast(cache)) { + if (!incomplete) { + wave->WriteWaveform(range, valid_ranges, &waveform); + } } - } else { - // Detect if this audio was incomplete because it was waiting on a conform to finish - if (watcher->GetTicket()->property("incomplete").toBool()) { + + if (incomplete) { if (last_conform_task_ > watcher_job_time) { // Requeue now - node->audio_playback_cache()->Invalidate(range); + cache->Invalidate(range); } else { // Wait for conform - d.needing_conform.insert(range); - } - } else { - if (AudioWaveformCache *cache = Node::ValueToPtr(watcher->property("cache"))) { - cache->WriteWaveform(range, valid_ranges, &waveform); + d.needs_conform.insert(range); } } } @@ -169,20 +184,16 @@ void PreviewAutoCacher::VideoRendered() // If the task list doesn't contain this watcher, presumably it was cleared as a result of a // viewer switch, so we'll completely ignore this watcher - auto it = video_tasks_.find(watcher); - - if (it != video_tasks_.end()) { + if (running_video_tasks_.removeOne(watcher)) { // Assume that a "result" is a fully completed image and a non-result is a cancelled ticket if (watcher->HasResult()) { if (watcher->GetTicket()->property("cached").toBool()) { if (FrameHashCache *cache = Node::ValueToPtr(watcher->property("cache"))) { - cache->ValidateTime(it.value()); + cache->ValidateTime(watcher->property("time").value()); } } } - video_tasks_.erase(it); - // Continue rendering TryRender(); } @@ -333,7 +344,7 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node) connect(node->thumbnail_cache(), &PlaybackCache::Request, this, - &PreviewAutoCacher::ThumbnailsInvalidatedFromCache); + &PreviewAutoCacher::VideoInvalidatedFromCache); connect(node->audio_playback_cache(), &PlaybackCache::Request, @@ -345,6 +356,16 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node) this, &PreviewAutoCacher::AudioInvalidatedFromCache); + connect(node->video_frame_cache(), + &PlaybackCache::CancelAll, + this, + &PreviewAutoCacher::CancelForCache); + + connect(node->audio_playback_cache(), + &PlaybackCache::CancelAll, + this, + &PreviewAutoCacher::CancelForCache); + node->video_frame_cache()->LoadState(); node->audio_playback_cache()->LoadState(); node->thumbnail_cache()->LoadState(); @@ -360,7 +381,7 @@ void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) disconnect(node->thumbnail_cache(), &PlaybackCache::Request, this, - &PreviewAutoCacher::ThumbnailsInvalidatedFromCache); + &PreviewAutoCacher::VideoInvalidatedFromCache); disconnect(node->audio_playback_cache(), &PlaybackCache::Request, @@ -372,6 +393,16 @@ void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) this, &PreviewAutoCacher::AudioInvalidatedFromCache); + disconnect(node->video_frame_cache(), + &PlaybackCache::CancelAll, + this, + &PreviewAutoCacher::CancelForCache); + + disconnect(node->audio_playback_cache(), + &PlaybackCache::CancelAll, + this, + &PreviewAutoCacher::CancelForCache); + node->video_frame_cache()->SaveState(); node->audio_playback_cache()->SaveState(); node->thumbnail_cache()->SaveState(); @@ -396,41 +427,29 @@ void PreviewAutoCacher::CancelQueuedSingleFrameRender() } } -void PreviewAutoCacher::VideoInvalidatedList(Node *node, const TimeRangeList &list) -{ - foreach (const TimeRange &range, list) { - VideoInvalidatedFromNode(node, range, PlaybackCache::kCacheOnly); - } -} - -void PreviewAutoCacher::AudioInvalidatedList(Node *node, const TimeRangeList &list) -{ - foreach (const TimeRange &range, list) { - AudioInvalidatedFromNode(node, range, PlaybackCache::kCacheOnly); - } -} - void PreviewAutoCacher::StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker) { range_list->insert(range); tracker->insert(range, graph_changed_time_); } -void PreviewAutoCacher::StartCachingVideoRange(Node *node, const TimeRange &range, PlaybackCache::RequestType type) +void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeRange &range) { - pending_video_jobs_.push_back({node, range, TimeRangeListFrameIterator({range}, viewer_node_->GetVideoParams().frame_rate_as_time_base()), type}); - video_cache_data_[node].job_tracker.insert(range, graph_changed_time_); + Node *node = cache->parent(); + pending_video_jobs_.push_back({node, cache, range, TimeRangeListFrameIterator({range}, viewer_node_->GetVideoParams().frame_rate_as_time_base())}); + video_cache_data_[cache].job_tracker.insert(range, graph_changed_time_); TryRender(); } -void PreviewAutoCacher::StartCachingAudioRange(Node *node, const TimeRange &range, PlaybackCache::RequestType type) +void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeRange &range) { - pending_audio_jobs_.push_back({node, range, type}); - audio_cache_data_[node].job_tracker.insert(range, graph_changed_time_); + Node *node = cache->parent(); + pending_audio_jobs_.push_back({node, cache, range}); + audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_); TryRender(); } -void PreviewAutoCacher::VideoInvalidatedFromNode(Node *node, const TimeRange &range, PlaybackCache::RequestType type) +void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const TimeRange &range) { // Stop any current render tasks because a) they might be out of date now anyway, and b) we // want to dedicate all our rendering power to realtime feedback for the user @@ -438,18 +457,18 @@ void PreviewAutoCacher::VideoInvalidatedFromNode(Node *node, const TimeRange &ra // If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames if (!NodeInputDragger::IsInputBeingDragged()) { - StartCachingVideoRange(node, range, type); + StartCachingVideoRange(cache, range); } } -void PreviewAutoCacher::AudioInvalidatedFromNode(Node *node, const TimeRange &range, PlaybackCache::RequestType type) +void PreviewAutoCacher::AudioInvalidatedFromNode(PlaybackCache *cache, const TimeRange &range) { // We don't stop rendering audio because currently there's no system of requeuing audio if it's // cancelled, so some areas may end up unrendered forever // ClearAudioQueue(); // If we're auto-caching audio or require realtime waveforms, we'll have to render this - StartCachingAudioRange(node, range, type); + StartCachingAudioRange(cache, range); } void PreviewAutoCacher::SetPlayhead(const rational &playhead) @@ -465,25 +484,25 @@ void CancelTasks(const T &task_list, bool and_wait) { for (auto it=task_list.cbegin(); it!=task_list.cend(); it++) { // Signal that the ticket should not be finished - it.key()->Cancel(); + (*it)->Cancel(); } if (and_wait) { // Wait for each ticket to finish for (auto it=task_list.cbegin(); it!=task_list.cend(); it++) { - it.key()->WaitForFinished(); + (*it)->WaitForFinished(); } } } void PreviewAutoCacher::CancelVideoTasks(bool and_wait_for_them_to_finish) { - CancelTasks(video_tasks_, and_wait_for_them_to_finish); + CancelTasks(running_video_tasks_, and_wait_for_them_to_finish); } void PreviewAutoCacher::CancelAudioTasks(bool and_wait_for_them_to_finish) { - CancelTasks(audio_tasks_, and_wait_for_them_to_finish); + CancelTasks(running_audio_tasks_, and_wait_for_them_to_finish); } bool PreviewAutoCacher::IsRenderingCustomRange() const @@ -545,8 +564,8 @@ void PreviewAutoCacher::TryRender() // 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. - if (!audio_tasks_.isEmpty() - || !video_tasks_.isEmpty()) { + if (!running_audio_tasks_.isEmpty() + || !running_video_tasks_.isEmpty()) { return; } @@ -558,7 +577,6 @@ void PreviewAutoCacher::TryRender() // Check if already caching this RenderTicketWatcher *watcher = RenderFrame(copied_viewer_node_->GetConnectedTextureOutput(), single_frame_render_->property("time").value(), - PlaybackCache::kCacheOnly, RenderTicketPriority(single_frame_render_->property("priority").toInt()), nullptr); video_immediate_passthroughs_[watcher].append(single_frame_render_); @@ -577,22 +595,8 @@ void PreviewAutoCacher::TryRender() if (Node *copy = copy_map_.value(d.node)) { // Queue next frames rational t; - while (video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { - RenderTicketWatcher* render_task = video_tasks_.key(t); - - // We want this hash, if we're not already rendering, start render now - if (!render_task) { - // Don't render any hash more than once - FrameHashCache *using_cache; - - if (d.type == PlaybackCache::kCacheOnly) { - using_cache = d.node->video_frame_cache(); - } else { - using_cache = d.node->thumbnail_cache(); - } - - RenderFrame(copy, t, d.type, RenderTicketPriority::kNormal, using_cache); - } + while (running_video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { + RenderFrame(copy, t, RenderTicketPriority::kNormal, d.cache); emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); @@ -617,13 +621,7 @@ void PreviewAutoCacher::TryRender() // Start job if (Node *copy = copy_map_.value(d.node)) { - PlaybackCache *cache; - if (d.type == PlaybackCache::kPreviewsOnly) { - cache = d.node->waveform_cache(); - } else { - cache = d.node->audio_playback_cache(); - } - RenderAudio(copy, d.range, d.type, RenderTicketPriority::kNormal, cache); + RenderAudio(copy, d.range, RenderTicketPriority::kNormal, d.cache); } else { qCritical() << "Failed to find node copy for audio job"; } @@ -633,14 +631,14 @@ void PreviewAutoCacher::TryRender() } } -RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, PlaybackCache::RequestType type, RenderTicketPriority priority, FrameHashCache *cache) +RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, RenderTicketPriority priority, PlaybackCache *cache) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); watcher->setProperty("cache", Node::PtrToValue(cache)); - watcher->setProperty("type", type); + watcher->setProperty("time", QVariant::fromValue(time)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered); - video_tasks_.insert(watcher, time); + running_video_tasks_.append(watcher); RenderManager::RenderVideoParams rvp(node, copied_viewer_node_->GetVideoParams(), @@ -648,18 +646,18 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& time, copied_color_manager_); - if (cache) { - if (type == PlaybackCache::kPreviewsOnly) { + if (FrameHashCache *frame_cache = dynamic_cast(cache)) { + if (ThumbnailCache *wave_cache = dynamic_cast(cache)) { rvp.video_params.set_divider(VideoParams::GetDividerForTargetResolution(rvp.video_params.width(), rvp.video_params.height(), 160, 120)); rvp.force_color_output = display_color_processor_; rvp.force_format = VideoParams::kFormatUnsigned8; - cache->SetTimebase(rational(1, 10)); + wave_cache->SetTimebase(rational(1, 10)); } else { - cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); + frame_cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); } - rvp.AddCache(cache); + rvp.AddCache(frame_cache); } rvp.priority = priority; @@ -671,21 +669,21 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& return watcher; } -RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, PlaybackCache::RequestType type, RenderTicketPriority priority, PlaybackCache *cache) +RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, RenderTicketPriority priority, PlaybackCache *cache) { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); watcher->setProperty("node", Node::PtrToValue(node)); - watcher->setProperty("type", type); watcher->setProperty("cache", Node::PtrToValue(cache)); + watcher->setProperty("time", QVariant::fromValue(r)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); - audio_tasks_.insert(watcher, r); + running_audio_tasks_.append(watcher); RenderManager::RenderAudioParams rap(node, r, copied_viewer_node_->GetAudioParams()); - rap.generate_waveforms = (type == PlaybackCache::kPreviewsOnly); + rap.generate_waveforms = dynamic_cast(cache); rap.priority = priority; rap.clamp = false; @@ -700,15 +698,10 @@ void PreviewAutoCacher::ConformFinished() last_conform_task_.Acquire(); for (auto it=audio_cache_data_.begin(); it!=audio_cache_data_.end(); it++) { - AudioCacheData &d = it.value(); - - if (!d.needing_conform.isEmpty()) { - // This list should be empty if there was a viewer switch - foreach (const TimeRange &range, d.needing_conform) { - it.key()->audio_playback_cache()->Invalidate(range); - } - d.needing_conform.clear(); + foreach (const TimeRange &range, it.value().needs_conform) { + it.key()->Request(range); } + it.value().needs_conform.clear(); } } @@ -725,7 +718,7 @@ void PreviewAutoCacher::ForceCacheRange(const TimeRange &range) custom_autocache_range_ = range; // Re-hash these frames and start rendering - StartCachingVideoRange(viewer_node_, range, PlaybackCache::kCacheOnly); + StartCachingVideoRange(viewer_node_->video_frame_cache(), range); } void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) @@ -742,17 +735,17 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) delayed_requeue_timer_.stop(); // Handle video rendering tasks - if (!video_tasks_.isEmpty()) { + if (!running_video_tasks_.isEmpty()) { // Cancel any video tasks and wait for them to finish CancelVideoTasks(true); - video_tasks_.clear(); + running_video_tasks_.clear(); } // Handle audio rendering tasks - if (!audio_tasks_.isEmpty()) { + if (!running_audio_tasks_.isEmpty()) { // Cancel any audio tasks and wait for them to finish CancelAudioTasks(true); - audio_tasks_.clear(); + running_audio_tasks_.clear(); } // Clear any single frame render that might be queued diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 1256d8106..18c688261 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -103,9 +103,9 @@ signals: private: void TryRender(); - RenderTicketWatcher *RenderFrame(Node *node, const rational &time, PlaybackCache::RequestType type, RenderTicketPriority priority, FrameHashCache *cache); + RenderTicketWatcher *RenderFrame(Node *node, const rational &time, RenderTicketPriority priority, PlaybackCache *cache); - RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, PlaybackCache::RequestType type, RenderTicketPriority priority, PlaybackCache *cache); + RenderTicketPtr RenderAudio(Node *node, const TimeRange &range, RenderTicketPriority priority, PlaybackCache *cache); /** * @brief Process all changes to internal NodeGraph copy @@ -132,15 +132,12 @@ private: void CancelQueuedSingleFrameRender(); - void VideoInvalidatedList(Node *node, const TimeRangeList &list); - void AudioInvalidatedList(Node *node, const TimeRangeList &list); - void StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker); - void StartCachingVideoRange(Node *node, const TimeRange &range, PlaybackCache::RequestType type); - void StartCachingAudioRange(Node *node, const TimeRange &range, PlaybackCache::RequestType type); + void StartCachingVideoRange(PlaybackCache *cache, const TimeRange &range); + void StartCachingAudioRange(PlaybackCache *cache, const TimeRange &range); - void VideoInvalidatedFromNode(Node *node, const olive::TimeRange &range, PlaybackCache::RequestType type); - void AudioInvalidatedFromNode(Node *node, const olive::TimeRange &range, PlaybackCache::RequestType type); + void VideoInvalidatedFromNode(PlaybackCache *cache, const olive::TimeRange &range); + void AudioInvalidatedFromNode(PlaybackCache *cache, const olive::TimeRange &range); class QueuedJob { public: @@ -187,14 +184,14 @@ private: JobTime last_conform_task_; - QMap audio_tasks_; - QMap video_tasks_; + QVector running_video_tasks_; + QVector running_audio_tasks_; struct VideoJob { Node *node; + PlaybackCache *cache; TimeRange range; TimeRangeListFrameIterator iterator; - PlaybackCache::RequestType type; }; struct VideoCacheData { @@ -203,20 +200,20 @@ private: struct AudioJob { Node *node; + PlaybackCache *cache; TimeRange range; - PlaybackCache::RequestType type; }; struct AudioCacheData { - TimeRangeList needing_conform; RenderJobTracker job_tracker; + TimeRangeList needs_conform; }; std::list pending_video_jobs_; std::list pending_audio_jobs_; - QHash video_cache_data_; - QHash audio_cache_data_; + QHash video_cache_data_; + QHash audio_cache_data_; ColorProcessorPtr display_color_processor_; @@ -225,12 +222,13 @@ private slots: * @brief Handler for when the NodeGraph reports a video change over a certain time range */ void VideoInvalidatedFromCache(const olive::TimeRange &range); - void ThumbnailsInvalidatedFromCache(const olive::TimeRange &range); /** * @brief Handler for when the NodeGraph reports a audio change over a certain time range */ - void AudioInvalidatedFromCache(const olive::TimeRange &range, olive::PlaybackCache::RequestType type); + void AudioInvalidatedFromCache(const olive::TimeRange &range); + + void CancelForCache(); /** * @brief Handler for when the RenderManager has returned rendered audio diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 371f1e89e..c0d899fd3 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -449,11 +449,6 @@ void ViewerWidget::UpdateAutoCacher() auto_cacher_->SetPlayhead(GetTime()); } -void ViewerWidget::ClearVideoAutoCacherQueue() -{ - auto_cacher_->CancelVideoTasks(); -} - void ViewerWidget::DecrementPrequeuedAudio() { prequeuing_audio_--; @@ -656,7 +651,7 @@ void ViewerWidget::QueueNoLongerStarved() void ViewerWidget::ForceRequeueFromCurrentTime() { - ClearVideoAutoCacherQueue(); + //ClearVideoAutoCacherQueue(); int queue = DeterminePlaybackQueueSize(); playback_queue_next_frame_ = GetTimestamp() + playback_speed_; for (int i=queue_watchers_.size(); i Date: Mon, 30 May 2022 22:08:29 -0700 Subject: [PATCH 14/53] reimplement sfr queue clearing --- app/render/previewautocacher.cpp | 12 ++++++++++++ app/render/previewautocacher.h | 2 ++ app/widget/viewer/viewer.cpp | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 1ace48877..caa604ef1 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -83,6 +83,18 @@ RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, RenderTicket return RenderAudio(copied_viewer_node_->GetConnectedSampleOutput(), range, priority, nullptr); } +void PreviewAutoCacher::ClearSingleFrameRenders() +{ + QMap > copy = video_immediate_passthroughs_; + for (auto it=copy.cbegin(); it!=copy.cend(); it++) { + it.key()->Cancel(); + if (!it.key()->IsRunning()) { + RenderManager::instance()->RemoveTicket(it.key()->GetTicket()); + emit it.key()->GetTicket()->Finished(); + } + } +} + void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range) { PlaybackCache *cache = static_cast(sender()); diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 18c688261..e5d95aa2f 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -55,6 +55,8 @@ public: RenderTicketPtr GetRangeOfAudio(TimeRange range, RenderTicketPriority prioritize); + void ClearSingleFrameRenders(); + /** * @brief Set the viewer node to auto-cache */ diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 64769237a..89dbd4d37 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -665,8 +665,7 @@ void ViewerWidget::ForceRequeueFromCurrentTime() // Allow half a second for requeue to complete static const rational kRequeueWaitTime(1); - qDebug() << "FIXME: Should be able to clear our frames"; - //ClearVideoAutoCacherQueue(); + auto_cacher_->ClearSingleFrameRenders(); queue_watchers_.clear(); int queue = DeterminePlaybackQueueSize(); playback_queue_next_frame_ = GetTimestamp() + playback_speed_ * Timecode::time_to_timestamp(kRequeueWaitTime, timebase(), Timecode::kFloor);; @@ -822,6 +821,7 @@ void ViewerWidget::PauseInternal() qDeleteAll(queue_watchers_); queue_watchers_.clear(); + auto_cacher_->ClearSingleFrameRenders(); playback_backup_timer_.stop(); From fefb16e49bd9483d00a0001ff605cc68bf460d7a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 1 Jun 2022 10:31:28 -0700 Subject: [PATCH 15/53] fixed diskmanager delete bug --- app/render/diskmanager.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/render/diskmanager.cpp b/app/render/diskmanager.cpp index 5cd8c0a00..cda301e83 100644 --- a/app/render/diskmanager.cpp +++ b/app/render/diskmanager.cpp @@ -314,9 +314,7 @@ bool DiskCacheFolder::DeleteFileInternal(QMap::iterator hash_ // Remove from disk QFile f(filename); - if (!f.exists()) { - return true; - } else if (f.remove()) { + if (!f.exists() || f.remove()) { // Remove from internal map disk_data_.erase(hash_to_delete); From 7a8794fe8850d2e41ba1c4e06b42e58c2e09cd88 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 3 Jun 2022 15:48:16 -0700 Subject: [PATCH 16/53] ui: draw clip cache on timeline --- app/node/block/clip/clip.cpp | 4 ++ app/node/block/clip/clip.h | 9 ++++ app/render/playbackcache.cpp | 30 ++++++++++++- app/render/playbackcache.h | 17 ++++++-- .../timelinewidget/view/timelineview.cpp | 7 +++ app/widget/timeruler/timeruler.cpp | 43 +++++-------------- app/widget/timeruler/timeruler.h | 2 - 7 files changed, 71 insertions(+), 41 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 018984bce..9d7044be5 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -292,6 +292,8 @@ void ClipBlock::InputConnectedEvent(const QString &input, int element, Node *out if (input == kBufferIn) { connect(output->thumbnail_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); connect(output->waveform_cache(), &AudioPlaybackCache::Validated, this, &Block::PreviewChanged); + connect(output->video_frame_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); + connect(output->audio_playback_cache(), &AudioPlaybackCache::Validated, this, &Block::PreviewChanged); } } @@ -302,6 +304,8 @@ void ClipBlock::InputDisconnectedEvent(const QString &input, int element, Node * if (input == kBufferIn) { disconnect(output->thumbnail_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); disconnect(output->waveform_cache(), &AudioPlaybackCache::Validated, this, &Block::PreviewChanged); + disconnect(output->video_frame_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); + disconnect(output->audio_playback_cache(), &AudioPlaybackCache::Validated, this, &Block::PreviewChanged); } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 26e76e5ce..114c556e0 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -122,6 +122,15 @@ public: return block_links_; } + const FrameHashCache *connected_video_cache() const + { + if (Node *n = GetConnectedOutput(kBufferIn)) { + return n->video_frame_cache(); + } else { + return nullptr; + } + } + const FrameHashCache *thumbnails() { if (Node *n = GetConnectedOutput(kBufferIn)) { diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 80384fb63..fc28b7a21 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -127,6 +127,32 @@ void PlaybackCache::SaveState() } } +void PlaybackCache::Draw(QPainter *p, const rational &start, double scale, const QRect &rect) const +{ + p->fillRect(rect, Qt::red); + + foreach (const TimeRange& range, GetValidatedRanges()) { + int range_left = rect.left() + (range.in() - start).toDouble() * scale; + if (range_left >= rect.right()) { + continue; + } + + int range_right = rect.left() + (range.out() - start).toDouble() * scale; + if (range_right < rect.left()) { + continue; + } + + int adjusted_left = std::max(range_left, rect.left()); + int adjusted_right = std::min(range_right, rect.right()); + + p->fillRect(adjusted_left, + rect.top(), + adjusted_right - adjusted_left, + rect.height(), + Qt::green); + } +} + void PlaybackCache::InvalidateAll() { Invalidate(TimeRange(0, RATIONAL_MAX)); @@ -156,7 +182,7 @@ PlaybackCache::PlaybackCache(QObject *parent) : uuid_ = QUuid::createUuid(); } -TimeRangeList PlaybackCache::GetInvalidatedRanges(TimeRange intersecting) +TimeRangeList PlaybackCache::GetInvalidatedRanges(TimeRange intersecting) const { TimeRangeList invalidated; @@ -174,7 +200,7 @@ TimeRangeList PlaybackCache::GetInvalidatedRanges(TimeRange intersecting) return invalidated; } -bool PlaybackCache::HasInvalidatedRanges(const TimeRange &intersecting) +bool PlaybackCache::HasInvalidatedRanges(const TimeRange &intersecting) const { return !validated_.contains(intersecting); } diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index c36edd1b3..2b49dc618 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -23,6 +23,7 @@ #include #include +#include #include #include "common/jobtime.h" @@ -43,14 +44,14 @@ public: const QUuid &GetUuid() const { return uuid_; } void SetUuid(const QUuid &u) { uuid_ = u; } - TimeRangeList GetInvalidatedRanges(TimeRange intersecting); - TimeRangeList GetInvalidatedRanges(const rational &length) + TimeRangeList GetInvalidatedRanges(TimeRange intersecting) const; + TimeRangeList GetInvalidatedRanges(const rational &length) const { return GetInvalidatedRanges(TimeRange(0, length)); } - bool HasInvalidatedRanges(const TimeRange &intersecting); - bool HasInvalidatedRanges(const rational &length) + bool HasInvalidatedRanges(const TimeRange &intersecting) const; + bool HasInvalidatedRanges(const rational &length) const { return HasInvalidatedRanges(TimeRange(0, length)); } @@ -59,6 +60,7 @@ public: void Invalidate(const TimeRange& r); + bool HasValidatedRanges() const { return !validated_.isEmpty(); } const TimeRangeList &GetValidatedRanges() const { return validated_; } Node *parent() const; @@ -69,6 +71,13 @@ public: void LoadState(); void SaveState(); + void Draw(QPainter *painter, const rational &start, double scale, const QRect &rect) const; + + static int GetCacheIndicatorHeight() + { + return QFontMetrics(QFont()).height()/4; + } + public slots: void InvalidateAll(); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 7adf8f38d..4f4167bdd 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -603,6 +603,13 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q } } } + + if (const FrameHashCache *cache = clip->connected_video_cache()) { + if (cache->HasValidatedRanges()) { + QRect cache_rect = r.adjusted(0, r.height() - PlaybackCache::GetCacheIndicatorHeight(), 0, 0).toRect(); + cache->Draw(painter, clip->media_in(), GetScale(), cache_rect); + } + } } // For transitions, show lines representing a transition diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index 642e160df..59a234176 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -46,7 +46,6 @@ TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* pare setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); // Text height is used to calculate widget height - cache_status_height_ = text_height() / 4; // Get the "minimum" space allowed between two line markers on the ruler (in screen pixels) // Mediocre but reliable way of scaling UI objects by font/DPI size @@ -181,7 +180,7 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) int line_bottom = height(); if (show_cache_status_) { - line_bottom -= cache_status_height_; + line_bottom -= PlaybackCache::GetCacheIndicatorHeight(); } int long_height = fm.height(); @@ -255,38 +254,16 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) // If cache status is enabled if (show_cache_status_ && playback_cache_) { // FIXME: Hardcoded to get video length, if we ever need audio length, this will have to change + int h = PlaybackCache::GetCacheIndicatorHeight(); + QRect cache_rect(0, height() - h, width(), h); + if (ViewerOutput *viewer = dynamic_cast(playback_cache_->parent())) { - rational len = viewer->GetVideoLength(); - int lim_left = GetScroll(); - int lim_right = lim_left + width(); + int right = TimeToScene(viewer->GetVideoLength()); + cache_rect.setWidth(std::max(0, right)); + } - int cache_screen_length = TimeToScene(len); - - if (cache_screen_length > 0) { - int cache_y = height() - cache_status_height_; - - p->fillRect(0, cache_y, cache_screen_length, cache_status_height_, Qt::green); - - foreach (const TimeRange& range, playback_cache_->GetInvalidatedRanges(len)) { - int range_left = TimeToScene(range.in()); - if (range_left >= width()) { - continue; - } - - int range_right = TimeToScene(range.out()); - if (range_right < 0) { - continue; - } - - int adjusted_left = qMax(lim_left, range_left); - - p->fillRect(adjusted_left, - cache_y, - qMin(lim_right, range_right) - adjusted_left, - cache_status_height_, - Qt::red); - } - } + if (cache_rect.width() > 0) { + playback_cache_->Draw(p, SceneToTime(GetScroll()), GetScale(), cache_rect); } } @@ -338,7 +315,7 @@ void TimeRuler::UpdateHeight() // Add cache status height if (show_cache_status_) { - height += cache_status_height_; + height += PlaybackCache::GetCacheIndicatorHeight(); } // Add marker height diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h index 372e0e602..f23571634 100644 --- a/app/widget/timeruler/timeruler.h +++ b/app/widget/timeruler/timeruler.h @@ -53,8 +53,6 @@ private: int CacheStatusHeight() const; - int cache_status_height_; - int minimum_gap_between_lines_; bool text_visible_; From 13299d0a3be6f254cf0daa2c6809cc4d7c8d6376 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 4 Jun 2022 17:29:09 -0700 Subject: [PATCH 17/53] cache: implemented base functionality for clip cache --- app/node/node.cpp | 9 ++++++ app/node/node.h | 2 ++ app/node/traverser.cpp | 43 ++++++++++++++++++------- app/node/traverser.h | 10 +++--- app/node/value.h | 2 ++ app/render/job/cachejob.h | 55 ++++++++++++++++++++++++++++++++ app/render/previewautocacher.cpp | 3 ++ app/render/renderprocessor.cpp | 19 +++++++++-- app/render/renderprocessor.h | 2 ++ 9 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 app/render/job/cachejob.h diff --git a/app/node/node.cpp b/app/node/node.cpp index 2b5220195..84f46ef96 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -234,6 +234,14 @@ void Node::DisconnectEdge(Node *output, const NodeInput &input) } } +void Node::CopyCacheUuidsFrom(Node *n) +{ + video_cache_->SetUuid(n->video_cache_->GetUuid()); + audio_cache_->SetUuid(n->audio_cache_->GetUuid()); + thumbnail_cache_->SetUuid(n->thumbnail_cache_->GetUuid()); + waveform_cache_->SetUuid(n->waveform_cache_->GetUuid()); +} + QString Node::GetInputName(const QString &id) const { const Input* i = GetInternalInputData(id); @@ -943,6 +951,7 @@ void Node::InvalidateCache(const TimeRange &range, const QString &from, int elem TimeRange ar = range.Intersected(GetAudioCacheRange()); if (ar.length() != 0) { audio_playback_cache()->Invalidate(ar); + waveform_cache()->Invalidate(ar); } } diff --git a/app/node/node.h b/app/node/node.h index c331d9b6d..d7a3292f9 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -353,6 +353,8 @@ public: static void DisconnectEdge(Node *output, const NodeInput& input); + void CopyCacheUuidsFrom(Node *n); + virtual QString GetInputName(const QString& id) const; void SetInputName(const QString& id, const QString& name); diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index ca4267d52..c043d0636 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -48,7 +48,7 @@ NodeValueRow NodeTraverser::GenerateRow(NodeValueDatabase *database, const Node NodeValueRow row; for (auto it=database->begin(); it!=database->end(); it++) { // Get hint for which value should be pulled - NodeValue value = GenerateRowValue(node, it.key(), &it.value()); + NodeValue value = GenerateRowValue(node, it.key(), &it.value(), range); row.insert(it.key(), value); } @@ -65,9 +65,9 @@ NodeValueRow NodeTraverser::GenerateRow(const Node *node, const TimeRange &range return GenerateRow(&database, node, range); } -NodeValue NodeTraverser::GenerateRowValue(const Node *node, const QString &input, NodeValueTable *table) +NodeValue NodeTraverser::GenerateRowValue(const Node *node, const QString &input, NodeValueTable *table, const TimeRange &time) { - NodeValue value = GenerateRowValueElement(node, input, -1, table); + NodeValue value = GenerateRowValueElement(node, input, -1, table, time); if (value.array()) { // Resolve each element of array @@ -75,7 +75,7 @@ NodeValue NodeTraverser::GenerateRowValue(const Node *node, const QString &input QVector output(tables.size()); for (int i=0; iGetValueHintForInput(input, element), node->GetInputDataType(input), table); -} - -NodeValue NodeTraverser::GenerateRowValueElement(const Node::ValueHint &hint, NodeValue::Type preferred_type, NodeValueTable *table) -{ - int value_index = GenerateRowValueElementIndex(hint, preferred_type, table); + int value_index = GenerateRowValueElementIndex(node->GetValueHintForInput(input, element), node->GetInputDataType(input), table); if (value_index == -1) { // If value was -1, try getting the last value @@ -103,7 +98,17 @@ NodeValue NodeTraverser::GenerateRowValueElement(const Node::ValueHint &hint, No return NodeValue(); } - return table->TakeAt(value_index); + NodeValue value = table->TakeAt(value_index); + + if (value.type() == NodeValue::kTexture) { + QString cache = node->video_frame_cache()->GetValidCacheFilename(time.in()); + if (!cache.isEmpty()) { + qDebug() << "pushing cache job"; + value.set_value(CacheJob(cache, value.data())); + } + } + + return value; } int NodeTraverser::GenerateRowValueElementIndex(const Node::ValueHint &hint, NodeValue::Type preferred_type, const NodeValueTable *table) @@ -358,6 +363,10 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR return table; } +TexturePtr NodeTraverser::ProcessVideoCacheJob(const CacheJob &val) +{ + return nullptr; +} QVector2D NodeTraverser::GenerateResolution() const { @@ -367,6 +376,16 @@ QVector2D NodeTraverser::GenerateResolution() const void NodeTraverser::ResolveJobs(NodeValue &val, const TimeRange &range) { if (val.type() == NodeValue::kTexture || val.type() == NodeValue::kSamples) { + if (val.canConvert()) { + CacheJob job = val.value(); + TexturePtr tex = ProcessVideoCacheJob(job); + if (tex) { + val.set_value(tex); + } else { + val.set_value(job.GetFallback()); + } + } + if (val.canConvert()) { ShaderJob job = val.value(); diff --git a/app/node/traverser.h b/app/node/traverser.h index 105955450..e5b736389 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -26,8 +26,9 @@ #include "codec/decoder.h" #include "common/cancelableobject.h" #include "node/output/track/track.h" -#include "render/job/footagejob.h" +#include "render/job/cachejob.h" #include "render/job/colortransformjob.h" +#include "render/job/footagejob.h" #include "value.h" namespace olive { @@ -44,9 +45,8 @@ public: NodeValueRow GenerateRow(NodeValueDatabase *database, const Node *node, const TimeRange &range); NodeValueRow GenerateRow(const Node *node, const TimeRange &range); - NodeValue GenerateRowValue(const Node *node, const QString &input, NodeValueTable *table); - NodeValue GenerateRowValueElement(const Node *node, const QString &input, int element, NodeValueTable *table); - NodeValue GenerateRowValueElement(const Node::ValueHint &hint, NodeValue::Type preferred_type, NodeValueTable *table); + NodeValue GenerateRowValue(const Node *node, const QString &input, NodeValueTable *table, const TimeRange &time); + NodeValue GenerateRowValueElement(const Node *node, const QString &input, int element, NodeValueTable *table, const TimeRange &time); int GenerateRowValueElementIndex(const Node::ValueHint &hint, NodeValue::Type preferred_type, const NodeValueTable *table); int GenerateRowValueElementIndex(const Node *node, const QString &input, int element, const NodeValueTable *table); @@ -101,6 +101,8 @@ protected: virtual void ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs){} + virtual TexturePtr ProcessVideoCacheJob(const CacheJob &val); + virtual TexturePtr CreateTexture(const VideoParams &p) { return CreateDummyTexture(p); diff --git a/app/node/value.h b/app/node/value.h index 6cf1e537d..9fbd4600e 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -225,6 +225,8 @@ public: data_ = QVariant::fromValue(v); } + const QVariant &data() const { return data_; } + template bool canConvert() const { diff --git a/app/render/job/cachejob.h b/app/render/job/cachejob.h new file mode 100644 index 000000000..67c6c36b9 --- /dev/null +++ b/app/render/job/cachejob.h @@ -0,0 +1,55 @@ +/*** + + 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 CACHEJOB_H +#define CACHEJOB_H + +#include +#include + +namespace olive { + +class CacheJob +{ +public: + CacheJob() = default; + CacheJob(const QString &filename, const QVariant &fallback = QVariant()) + { + filename_ = filename; + } + + const QString &GetFilename() const { return filename_; } + void SetFilename(const QString &s) { filename_ = s; } + + const QVariant &GetFallback() const { return fallback_; } + void SetFallback(const QVariant &val) { fallback_ = val; } + +private: + QString filename_; + + QVariant fallback_; + +}; + +} + +Q_DECLARE_METATYPE(olive::CacheJob) + +#endif // CACHEJOB_H diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index caa604ef1..f3b4c23f9 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -269,6 +269,9 @@ void PreviewAutoCacher::AddNode(Node *node) // Add to project copy->setParent(&copied_project_); + // Copy cache UUIDs + copy->CopyCacheUuidsFrom(node); + // Insert into map InsertIntoCopyMap(node, copy); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index e89f3bbc6..4402042d3 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -530,9 +530,10 @@ void RenderProcessor::ProcessSamples(SampleBuffer &destination, const Node *node // Update all non-sample and non-footage inputs for (auto j=job.GetValues().constBegin(); j!=job.GetValues().constEnd(); j++) { - NodeValueTable value = ProcessInput(node, j.key(), TimeRange(this_sample_time, this_sample_time)); + TimeRange r = TimeRange(this_sample_time, this_sample_time); + NodeValueTable value = ProcessInput(node, j.key(), r); - value_db.insert(j.key(), GenerateRowValue(node, j.key(), &value)); + value_db.insert(j.key(), GenerateRowValue(node, j.key(), &value, r)); } node->ProcessSamples(value_db, @@ -559,6 +560,20 @@ void RenderProcessor::ProcessFrameGeneration(TexturePtr destination, const Node destination->Upload(frame->data(), frame->linesize_pixels()); } +TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob &val) +{ + FramePtr frame = FrameHashCache::LoadCacheFrame(val.GetFilename()); + if (frame) { + TexturePtr tex = CreateTexture(frame->video_params()); + if (tex) { + tex->Upload(frame->data(), frame->linesize_pixels()); + return tex; + } + } + + return nullptr; +} + void RenderProcessor::ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs) { ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager")); diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index db147bd3e..c2fc5d6c7 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -56,6 +56,8 @@ protected: virtual void ProcessFrameGeneration(TexturePtr destination, const Node *node, const GenerateJob& job) override; + virtual TexturePtr ProcessVideoCacheJob(const CacheJob &val) override; + virtual TexturePtr CreateTexture(const VideoParams &p) override { return render_ctx_->CreateTexture(p); From 25699ef05b0270c3d9141f3ee83604462cf816ca Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 5 Jun 2022 19:47:33 -0700 Subject: [PATCH 18/53] cache: use disk states to share caches --- app/node/block/clip/clip.cpp | 8 +++- app/node/graph.cpp | 4 ++ app/node/node.cpp | 26 ++++++------ app/node/node.h | 17 +++----- app/node/project/footage/footage.cpp | 2 - .../project/serializer/serializer220403.cpp | 9 ++++ app/node/traverser.cpp | 5 ++- app/render/framehashcache.cpp | 41 ++++++++++++++++--- app/render/playbackcache.cpp | 13 +++++- app/render/playbackcache.h | 7 +++- app/render/previewautocacher.cpp | 11 ++--- app/render/renderer.cpp | 2 + app/render/renderprocessor.cpp | 12 ++++-- app/widget/timeruler/timeruler.cpp | 2 +- 14 files changed, 109 insertions(+), 50 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 9d7044be5..28103720d 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -215,7 +215,9 @@ void ClipBlock::RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange { if ((range.in() == RATIONAL_MIN && range.out() == RATIONAL_MAX) || !range.length().isNull()) { // Request only this range - emit cache->Request(range.Intersected(max_range)); + TimeRange r = range.Intersected(max_range); + cache->Invalidate(r); + emit cache->Request(r); } else { // Request all ranges currently marked as invalid TimeRangeList invalid = cache->GetInvalidatedRanges(max_range); @@ -232,7 +234,9 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int // If signal is from texture input, transform all times from media time to sequence time if (from == kBufferIn) { // Render caches where necessary - RequestInvalidatedFromConnected(range); + if (AreCachesEnabled()) { + RequestInvalidatedFromConnected(range); + } // Adjust range from media time to sequence time TimeRange adj; diff --git a/app/node/graph.cpp b/app/node/graph.cpp index b4cdf44bd..44165727f 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -39,6 +39,10 @@ void NodeGraph::Clear() { // By deleting the last nodes first, we assume that nodes that are most important are deleted last // (e.g. Project's ColorManager or ProjectSettingsNode. + for (auto it=node_children_.cbegin(); it!=node_children_.cend(); it++) { + (*it)->SetCachesEnabled(false); + } + while (!node_children_.isEmpty()) { delete node_children_.last(); } diff --git a/app/node/node.cpp b/app/node/node.cpp index 84f46ef96..7b2edc383 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -48,8 +48,8 @@ Node::Node() : can_be_deleted_(true), override_color_(-1), folder_(nullptr), - cache_result_(false), - flags_(kNone) + flags_(kNone), + caches_enabled_(true) { AddInput(kEnabledInput, NodeValue::kBoolean, true); @@ -942,16 +942,18 @@ void Node::InvalidateCache(const TimeRange &range, const QString &from, int elem Q_UNUSED(from) Q_UNUSED(element) - if (range.in() != range.out()) { - TimeRange vr = range.Intersected(GetVideoCacheRange()); - if (vr.length() != 0) { - video_frame_cache()->Invalidate(vr); - thumbnail_cache()->Invalidate(vr); - } - TimeRange ar = range.Intersected(GetAudioCacheRange()); - if (ar.length() != 0) { - audio_playback_cache()->Invalidate(ar); - waveform_cache()->Invalidate(ar); + if (AreCachesEnabled()) { + if (range.in() != range.out()) { + TimeRange vr = range.Intersected(GetVideoCacheRange()); + if (vr.length() != 0) { + video_frame_cache()->Invalidate(vr); + thumbnail_cache()->Invalidate(vr); + } + TimeRange ar = range.Intersected(GetAudioCacheRange()); + if (ar.length() != 0) { + audio_playback_cache()->Invalidate(ar); + waveform_cache()->Invalidate(ar); + } } } diff --git a/app/node/node.h b/app/node/node.h index d7a3292f9..2ca325d34 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -355,6 +355,9 @@ public: void CopyCacheUuidsFrom(Node *n); + bool AreCachesEnabled() const { return caches_enabled_; } + void SetCachesEnabled(bool e) { caches_enabled_ = e; } + virtual QString GetInputName(const QString& id) const; void SetInputName(const QString& id, const QString& name); @@ -930,16 +933,6 @@ public: folder_ = folder; } - bool GetCacheTextures() const - { - return cache_result_; - } - - void SetCacheTextures(bool e) - { - cache_result_ = e; - } - class ArrayRemoveCommand : public UndoCommand { public: @@ -1405,8 +1398,6 @@ private: Folder* folder_; - bool cache_result_; - QMap value_hints_; PositionMap context_positions_; @@ -1423,6 +1414,8 @@ private: AudioPlaybackCache *audio_cache_; AudioWaveformCache *waveform_cache_; + bool caches_enabled_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index d38b610c0..231431619 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -47,8 +47,6 @@ Footage::Footage(const QString &filename) : valid_(false), cancelled_(nullptr) { - SetCacheTextures(true); - PrependInput(kLoopModeInput, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); PrependInput(kFilenameInput, NodeValue::kFile, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index a78e11b5b..54b1c60b0 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -90,7 +90,11 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project qWarning() << "Failed to find node with ID" << id; reader->skipCurrentElement(); } else { + // Disable cache while node is being loaded (we'll re-enable it later) + node->SetCachesEnabled(false); + LoadNode(node, xml_node_data, reader); + node->setParent(project); } } @@ -312,6 +316,11 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project } } + // Re-enable caches + for (Node *n : project->nodes()) { + n->SetCachesEnabled(true); + } + return load_data; } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index c043d0636..568a7fa18 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -101,9 +101,12 @@ NodeValue NodeTraverser::GenerateRowValueElement(const Node *node, const QString NodeValue value = table->TakeAt(value_index); if (value.type() == NodeValue::kTexture) { + QMutexLocker locker(node->video_frame_cache()->mutex()); + + node->video_frame_cache()->LoadState(); + QString cache = node->video_frame_cache()->GetValidCacheFilename(time.in()); if (!cache.isEmpty()) { - qDebug() << "pushing cache job"; value.set_value(CacheJob(cache, value.data())); } } diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index decdbcc3d..adab73bfc 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -177,15 +177,46 @@ FramePtr FrameHashCache::LoadCacheFrame(const QString &fn) } file.setFrameBuffer(framebuffer); + file.readPixels(dw.min.y, dw.max.y); } catch (const std::exception &e) { - qCritical() << "Failed to read cache frame:" << e.what(); + // Not an EXR, maybe it's a JPEG? + QImage img; - // Clear frame to signal that nothing was loaded - frame = nullptr; + if (img.load(fn, "jpg")) { - // Assume this frame is corrupt in some way and delete it - QMetaObject::invokeMethod(DiskManager::instance(), "DeleteSpecificFile", Q_ARG(QString, fn)); + // FIXME: Hardcoded + const int div = 1; + const VideoParams::Format image_format = VideoParams::kFormatUnsigned8; + const int channel_count = 4; + const rational par(1, 1); + + frame = Frame::Create(); + frame->set_video_params(VideoParams(img.width() * div, + img.height() * div, + image_format, + channel_count, + par, + VideoParams::kInterlaceNone, + div)); + + frame->allocate(); + + for (int i=0; idata() + frame->linesize_bytes() * i, + img.bits() + img.bytesPerLine() * i, + frame->width() * frame->video_params().GetBytesPerPixel()); + } + + } else { + qCritical() << "Failed to read cache frame:" << e.what(); + + // Clear frame to signal that nothing was loaded + frame = nullptr; + + // Assume this frame is corrupt in some way and delete it + QMetaObject::invokeMethod(DiskManager::instance(), "DeleteSpecificFile", Q_ARG(QString, fn)); + } } } diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index fc28b7a21..5ccbf40a0 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -39,6 +39,8 @@ void PlaybackCache::Invalidate(const TimeRange &r) InvalidateEvent(r); emit Invalidated(r); + + SaveState(); } Node *PlaybackCache::parent() const @@ -89,8 +91,6 @@ void PlaybackCache::LoadState() } f.close(); - - f.close(); } } @@ -165,6 +165,8 @@ void PlaybackCache::Validate(const TimeRange &r, bool signal) if (signal) { emit Validated(r); } + + SaveState(); } void PlaybackCache::InvalidateEvent(const TimeRange &) @@ -182,6 +184,13 @@ PlaybackCache::PlaybackCache(QObject *parent) : uuid_ = QUuid::createUuid(); } +void PlaybackCache::SetUuid(const QUuid &u) +{ + uuid_ = u; + + LoadState(); +} + TimeRangeList PlaybackCache::GetInvalidatedRanges(TimeRange intersecting) const { TimeRangeList invalidated; diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index 2b49dc618..509db8dda 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -22,6 +22,7 @@ #define PLAYBACKCACHE_H #include +#include #include #include #include @@ -42,7 +43,7 @@ public: PlaybackCache(QObject* parent = nullptr); const QUuid &GetUuid() const { return uuid_; } - void SetUuid(const QUuid &u) { uuid_ = u; } + void SetUuid(const QUuid &u); TimeRangeList GetInvalidatedRanges(TimeRange intersecting) const; TimeRangeList GetInvalidatedRanges(const rational &length) const @@ -78,6 +79,8 @@ public: return QFontMetrics(QFont()).height()/4; } + QMutex *mutex() { return &mutex_; } + public slots: void InvalidateAll(); @@ -106,6 +109,8 @@ private: QUuid uuid_; + QMutex mutex_; + }; } diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index f3b4c23f9..b2cf4f4d2 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -269,6 +269,9 @@ void PreviewAutoCacher::AddNode(Node *node) // Add to project copy->setParent(&copied_project_); + // Disable caches for copy + copy->SetCachesEnabled(false); + // Copy cache UUIDs copy->CopyCacheUuidsFrom(node); @@ -380,10 +383,6 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node) &PlaybackCache::CancelAll, this, &PreviewAutoCacher::CancelForCache); - - node->video_frame_cache()->LoadState(); - node->audio_playback_cache()->LoadState(); - node->thumbnail_cache()->LoadState(); } void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) @@ -417,10 +416,6 @@ void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) &PlaybackCache::CancelAll, this, &PreviewAutoCacher::CancelForCache); - - node->video_frame_cache()->SaveState(); - node->audio_playback_cache()->SaveState(); - node->thumbnail_cache()->SaveState(); } void PreviewAutoCacher::UpdateGraphChangeValue() diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index 8ebdedb01..5bc61b653 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -74,6 +74,8 @@ TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const V QVariant Renderer::GetDefaultShader() { + QMutexLocker locker(&color_cache_mutex_); + if (default_shader_.isNull()) { default_shader_ = CreateNativeShader(ShaderCode(QString(), QString())); } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 4402042d3..75ee420d2 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -253,21 +253,25 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c qint64 file_last_modified = QFileInfo(stream.filename()).lastModified().toMSecsSinceEpoch(); - if (!decoder.decoder || decoder.last_modified != file_last_modified) { + DecoderPtr dec = nullptr; + + if (decoder.decoder && decoder.last_modified == file_last_modified) { + dec = decoder.decoder; + } else { // No decoder - decoder.decoder = Decoder::CreateFromID(decoder_id); + decoder.decoder = dec = Decoder::CreateFromID(decoder_id); decoder.last_modified = file_last_modified; decoder_cache_->insert(stream, decoder); locker.unlock(); - if (!decoder.decoder->Open(stream)) { + if (!dec->Open(stream)) { qWarning() << "Failed to open decoder for" << stream.filename() << "::" << stream.stream(); return nullptr; } } - return decoder.decoder; + return dec; } void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache *decoder_cache, ShaderCache *shader_cache) diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index 59a234176..9e42c688b 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -252,7 +252,7 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) } // If cache status is enabled - if (show_cache_status_ && playback_cache_) { + if (show_cache_status_ && playback_cache_ && playback_cache_->HasValidatedRanges()) { // FIXME: Hardcoded to get video length, if we ever need audio length, this will have to change int h = PlaybackCache::GetCacheIndicatorHeight(); QRect cache_rect(0, height() - h, width(), h); From dfc401b63c26508d7e74286284152395f3de49f4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 6 Jun 2022 09:39:09 -0700 Subject: [PATCH 19/53] cache: behavior improvements --- app/node/block/clip/clip.cpp | 26 +++++++++++++------------- app/node/block/clip/clip.h | 2 +- app/node/node.cpp | 2 ++ app/render/playbackcache.cpp | 11 ++++++++--- app/render/playbackcache.h | 5 +++++ 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 28103720d..d0bbef560 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -192,37 +192,37 @@ void ClipBlock::RequestInvalidatedFromConnected(const TimeRange &range) TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); if (type == Track::kVideo) { // Handle thumbnails - RequestInvalidatedForCache(connected->thumbnail_cache(), max_range, range); + RequestInvalidatedForCache(connected->thumbnail_cache(), max_range, range, true); // Handle video cache - if (IsAutocaching()) { - RequestInvalidatedForCache(connected->video_frame_cache(), max_range, range); - } + RequestInvalidatedForCache(connected->video_frame_cache(), max_range, range, IsAutocaching()); } else if (type == Track::kAudio) { // Handle waveforms - RequestInvalidatedForCache(connected->waveform_cache(), max_range, range); + RequestInvalidatedForCache(connected->waveform_cache(), max_range, range, true); // Handle audio cache - if (IsAutocaching()) { - RequestInvalidatedForCache(connected->audio_playback_cache(), max_range, range); - } + RequestInvalidatedForCache(connected->audio_playback_cache(), max_range, range, IsAutocaching()); } } } } -void ClipBlock::RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range) +void ClipBlock::RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range, bool request) { if ((range.in() == RATIONAL_MIN && range.out() == RATIONAL_MAX) || !range.length().isNull()) { // Request only this range TimeRange r = range.Intersected(max_range); cache->Invalidate(r); - emit cache->Request(r); + if (request) { + emit cache->Request(r); + } } else { // Request all ranges currently marked as invalid - TimeRangeList invalid = cache->GetInvalidatedRanges(max_range); - for (const TimeRange &r : invalid) { - emit cache->Request(r); + if (request) { + TimeRangeList invalid = cache->GetInvalidatedRanges(max_range); + for (const TimeRange &r : invalid) { + emit cache->Request(r); + } } } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 114c556e0..f7135676a 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -196,7 +196,7 @@ private: void RequestInvalidatedFromConnected(const TimeRange &range = TimeRange()); - void RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range); + void RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range, bool request); QVector block_links_; diff --git a/app/node/node.cpp b/app/node/node.cpp index 7b2edc383..1c2ec5c4e 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -57,6 +57,8 @@ Node::Node() : thumbnail_cache_ = new ThumbnailCache(this); audio_cache_ = new AudioPlaybackCache(this); waveform_cache_ = new AudioWaveformCache(this); + + waveform_cache_->SetSavingEnabled(false); } Node::~Node() diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 5ccbf40a0..1dd472bed 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -40,7 +40,9 @@ void PlaybackCache::Invalidate(const TimeRange &r) emit Invalidated(r); - SaveState(); + if (saving_enabled_) { + SaveState(); + } } Node *PlaybackCache::parent() const @@ -166,7 +168,9 @@ void PlaybackCache::Validate(const TimeRange &r, bool signal) emit Validated(r); } - SaveState(); + if (saving_enabled_) { + SaveState(); + } } void PlaybackCache::InvalidateEvent(const TimeRange &) @@ -179,7 +183,8 @@ Project *PlaybackCache::GetProject() const } PlaybackCache::PlaybackCache(QObject *parent) : - QObject(parent) + QObject(parent), + saving_enabled_(true) { uuid_ = QUuid::createUuid(); } diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index 509db8dda..ac88e97e6 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -79,6 +79,9 @@ public: return QFontMetrics(QFont()).height()/4; } + bool IsSavingEnabled() const { return saving_enabled_; } + void SetSavingEnabled(bool e) { saving_enabled_ = e; } + QMutex *mutex() { return &mutex_; } public slots: @@ -109,6 +112,8 @@ private: QUuid uuid_; + bool saving_enabled_; + QMutex mutex_; }; From eeea16c5c36c3ac08ae64ebef40f8e9c76c0f123 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 6 Jun 2022 20:06:43 -0700 Subject: [PATCH 20/53] cache: add passthroughs to allow shallow copies of data --- app/node/block/clip/clip.cpp | 101 ++++++++++++++---- app/node/block/clip/clip.h | 27 +++-- app/render/audiowaveformcache.cpp | 11 ++ app/render/audiowaveformcache.h | 2 + app/render/framehashcache.cpp | 21 ++++ app/render/framehashcache.h | 11 +- app/render/playbackcache.cpp | 64 +++++++++-- app/render/playbackcache.h | 16 +++ app/widget/timelinewidget/tool/pointer.cpp | 3 +- .../timelinewidget/undo/timelineundosplit.cpp | 24 ++--- .../timelinewidget/undo/timelineundosplit.h | 2 + 11 files changed, 223 insertions(+), 59 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index d0bbef560..54d195f49 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -183,7 +183,7 @@ rational ClipBlock::MediaToSequenceTime(const rational &media_time) const return sequence_time; } -void ClipBlock::RequestInvalidatedFromConnected(const TimeRange &range) +void ClipBlock::RequestRangeFromConnected(const TimeRange &range) { Track::Type type = GetTrackType(); @@ -192,41 +192,75 @@ void ClipBlock::RequestInvalidatedFromConnected(const TimeRange &range) TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); if (type == Track::kVideo) { // Handle thumbnails - RequestInvalidatedForCache(connected->thumbnail_cache(), max_range, range, true); + RequestRangeForCache(connected->thumbnail_cache(), max_range, range, true, true); // Handle video cache - RequestInvalidatedForCache(connected->video_frame_cache(), max_range, range, IsAutocaching()); + RequestRangeForCache(connected->video_frame_cache(), max_range, range, true, IsAutocaching()); } else if (type == Track::kAudio) { // Handle waveforms - RequestInvalidatedForCache(connected->waveform_cache(), max_range, range, true); + RequestRangeForCache(connected->waveform_cache(), max_range, range, true, true); // Handle audio cache - RequestInvalidatedForCache(connected->audio_playback_cache(), max_range, range, IsAutocaching()); + RequestRangeForCache(connected->audio_playback_cache(), max_range, range, true, IsAutocaching()); } } } } -void ClipBlock::RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range, bool request) +void ClipBlock::RequestInvalidatedFromConnected() { - if ((range.in() == RATIONAL_MIN && range.out() == RATIONAL_MAX) || !range.length().isNull()) { - // Request only this range - TimeRange r = range.Intersected(max_range); - cache->Invalidate(r); - if (request) { - emit cache->Request(r); - } - } else { - // Request all ranges currently marked as invalid - if (request) { - TimeRangeList invalid = cache->GetInvalidatedRanges(max_range); - for (const TimeRange &r : invalid) { - emit cache->Request(r); + Track::Type type = GetTrackType(); + + if (type == Track::kVideo || type == Track::kAudio) { + if (Node *connected = GetConnectedOutput(kBufferIn)) { + TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); + if (type == Track::kVideo) { + // Handle thumbnails + RequestInvalidatedForCache(connected->thumbnail_cache(), max_range); + + // Handle video cache + if (IsAutocaching()) { + RequestInvalidatedForCache(connected->video_frame_cache(), max_range); + } + } else if (type == Track::kAudio) { + // Handle waveforms + RequestInvalidatedForCache(connected->waveform_cache(), max_range); + + // Handle audio cache + if (IsAutocaching()) { + RequestInvalidatedForCache(connected->audio_playback_cache(), max_range); + } } } } } +void ClipBlock::RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range, bool invalidate, bool request) +{ + TimeRange r = range.Intersected(max_range); + + if (invalidate) { + cache->Invalidate(r); + } + + if (request) { + emit cache->Request(r); + } +} + +void ClipBlock::RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range) +{ + TimeRangeList invalid = cache->GetInvalidatedRanges(max_range); + + for (const PlaybackCache::Passthrough &p : cache->GetPassthroughs()) { + invalid.remove(p); + } + + for (const TimeRange &r : invalid) { + RequestRangeForCache(cache, max_range, r, false, true); + } +} + void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) { Q_UNUSED(element) @@ -235,7 +269,7 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int if (from == kBufferIn) { // Render caches where necessary if (AreCachesEnabled()) { - RequestInvalidatedFromConnected(range); + RequestRangeFromConnected(range); } // Adjust range from media time to sequence time @@ -380,6 +414,33 @@ void ClipBlock::Retranslate() SetInputName(kMaintainAudioPitchInput, tr("Maintain Audio Pitch")); } +void ClipBlock::AddCachePassthroughFrom(ClipBlock *other) +{ + if (auto tc = this->video_frame_cache()) { + if (auto oc = other->video_frame_cache()) { + tc->SetPassthrough(oc); + } + } + + if (auto tc = this->audio_playback_cache()) { + if (auto oc = other->audio_playback_cache()) { + tc->SetPassthrough(oc); + } + } + + if (auto tc = this->thumbnails()) { + if (auto oc = other->thumbnails()) { + tc->SetPassthrough(oc); + } + } + + if (auto tc = this->waveform()) { + if (auto oc = other->waveform()) { + tc->SetPassthrough(oc); + } + } +} + void ClipBlock::ConnectedToPreviewEvent() { RequestInvalidatedFromConnected(); diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index f7135676a..e86496104 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -122,7 +122,7 @@ public: return block_links_; } - const FrameHashCache *connected_video_cache() const + FrameHashCache *connected_video_cache() const { if (Node *n = GetConnectedOutput(kBufferIn)) { return n->video_frame_cache(); @@ -131,7 +131,16 @@ public: } } - const FrameHashCache *thumbnails() + AudioPlaybackCache *connected_audio_cache() const + { + if (Node *n = GetConnectedOutput(kBufferIn)) { + return n->audio_playback_cache(); + } else { + return nullptr; + } + } + + FrameHashCache *thumbnails() { if (Node *n = GetConnectedOutput(kBufferIn)) { return n->thumbnail_cache(); @@ -140,7 +149,7 @@ public: } } - const AudioWaveformCache *waveform() + AudioWaveformCache *waveform() { if (Node *n = GetConnectedOutput(kBufferIn)) { return n->waveform_cache(); @@ -149,11 +158,7 @@ public: } } - void set_waveform(const AudioVisualWaveform *w) - { - qDebug() << "WAVEFORM COPY STUB"; - //audio_playback_cache()->set_visual(w); - } + void AddCachePassthroughFrom(ClipBlock *other); ViewerOutput *connected_viewer() const { @@ -194,9 +199,11 @@ private: rational MediaToSequenceTime(const rational& media_time) const; - void RequestInvalidatedFromConnected(const TimeRange &range = TimeRange()); + void RequestRangeFromConnected(const TimeRange &range); + void RequestInvalidatedFromConnected(); - void RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range, bool request); + void RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range, bool invalidate, bool request); + void RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range); QVector block_links_; diff --git a/app/render/audiowaveformcache.cpp b/app/render/audiowaveformcache.cpp index ba3a0f506..5b4898f7d 100644 --- a/app/render/audiowaveformcache.cpp +++ b/app/render/audiowaveformcache.cpp @@ -98,4 +98,15 @@ rational AudioWaveformCache::length() const return len; } +void AudioWaveformCache::SetPassthrough(PlaybackCache *cache) +{ + AudioWaveformCache *c = static_cast(cache); + waveforms_ = c->waveforms_; + for (const TimeRange &r : c->GetValidatedRanges()) { + Validate(r); + } + SetParameters(c->GetParameters()); + SetSavingEnabled(c->IsSavingEnabled()); +} + } diff --git a/app/render/audiowaveformcache.h b/app/render/audiowaveformcache.h index 0a27eb175..6dcf58f07 100644 --- a/app/render/audiowaveformcache.h +++ b/app/render/audiowaveformcache.h @@ -43,6 +43,8 @@ public: rational length() const; + virtual void SetPassthrough(PlaybackCache *cache) override; + private: class TimeRangeWithWaveform : public TimeRange { diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index adab73bfc..90f5bd858 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -64,6 +64,21 @@ void FrameHashCache::ValidateTime(const rational &time) Validate(TimeRange(time, time + timebase_)); } +QString FrameHashCache::GetValidCacheFilename(const rational &time) const +{ + if (IsFrameCached(time)) { + return CachePathName(time); + } else if (!GetPassthroughs().empty()) { + for (const Passthrough &p : GetPassthroughs()) { + if (p.Contains(time)) { + return CachePathName(GetCacheDirectory(), p.cache, time, timebase_); + } + } + } + + return QString(); +} + bool FrameHashCache::SaveCacheFrame(const int64_t &time, FramePtr frame) const { return SaveCacheFrame(GetCacheDirectory(), GetUuid(), time, frame); @@ -224,6 +239,12 @@ FramePtr FrameHashCache::LoadCacheFrame(const QString &fn) return frame; } +void FrameHashCache::SetPassthrough(PlaybackCache *cache) +{ + super::SetPassthrough(cache); + SetTimebase(static_cast(cache)->GetTimebase()); +} + void FrameHashCache::LoadStateEvent(QDataStream &stream) { uint32_t version; diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index e6a726dbb..b98460c49 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -48,14 +48,7 @@ public: return GetValidatedRanges().contains(time); } - QString GetValidCacheFilename(const rational &time) const - { - if (IsFrameCached(time)) { - return CachePathName(time); - } else { - return QString(); - } - } + QString GetValidCacheFilename(const rational &time) const; static bool SaveCacheFrame(const QString& filename, FramePtr frame); bool SaveCacheFrame(const int64_t &time, FramePtr frame) const; @@ -65,6 +58,8 @@ public: FramePtr LoadCacheFrame(const int64_t &time) const; static FramePtr LoadCacheFrame(const QString& fn); + virtual void SetPassthrough(PlaybackCache *cache) override; + protected: virtual void LoadStateEvent(QDataStream &stream) override; virtual void SaveStateEvent(QDataStream &stream) override; diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 1dd472bed..2de3a6447 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -36,6 +36,10 @@ void PlaybackCache::Invalidate(const TimeRange &r) validated_.remove(r); + if (!passthroughs_.empty()) { + TimeRangeList::util_remove(&passthroughs_, r); + } + InvalidateEvent(r); emit Invalidated(r); @@ -72,14 +76,14 @@ void PlaybackCache::LoadState() LoadStateEvent(s); - int count; - s >> count; - switch (version) { case 1: - validated_.clear(); + { + int valid_count, pass_count; - for (int i=0; i> valid_count; + for (int i=0; i> in_num; @@ -89,8 +93,27 @@ void PlaybackCache::LoadState() validated_.insert(TimeRange(rational(in_num, in_den), rational(out_num, out_den))); } + + passthroughs_.clear(); + s >> pass_count; + for (int i=0; i> in_num; + s >> in_den; + s >> out_num; + s >> out_den; + s >> id; + + Passthrough p = TimeRange(rational(in_num, in_den), rational(out_num, out_den)); + p.cache = id; + passthroughs_.append(p); + } + break; } + } f.close(); } @@ -100,7 +123,7 @@ void PlaybackCache::SaveState() { QDir cache_dir = GetThisCacheDirectory(); QFile f(cache_dir.filePath(QStringLiteral("state"))); - if (validated_.isEmpty()) { + if (validated_.isEmpty() && passthroughs_.isEmpty()) { if (f.exists()) { f.remove(); } @@ -123,6 +146,16 @@ void PlaybackCache::SaveState() s << r.out().denominator(); } + s << passthroughs_.size(); + + for (const Passthrough &p : passthroughs_) { + s << p.in().numerator(); + s << p.in().denominator(); + s << p.out().numerator(); + s << p.out().denominator(); + s << p.cache; + } + f.close(); } } @@ -155,6 +188,21 @@ void PlaybackCache::Draw(QPainter *p, const rational &start, double scale, const } } +void PlaybackCache::SetPassthrough(PlaybackCache *cache) +{ + for (const TimeRange &r : cache->GetValidatedRanges()) { + Passthrough p = r; + p.cache = cache->GetUuid(); + passthroughs_.push_back(p); + } + + passthroughs_.append(cache->GetPassthroughs()); + + if (saving_enabled_) { + SaveState(); + } +} + void PlaybackCache::InvalidateAll() { Invalidate(TimeRange(0, RATIONAL_MAX)); @@ -211,6 +259,10 @@ TimeRangeList PlaybackCache::GetInvalidatedRanges(TimeRange intersecting) const invalidated.remove(range); } + foreach (const TimeRange &range, passthroughs_) { + invalidated.remove(range); + } + return invalidated; } diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index ac88e97e6..d96709355 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -82,8 +82,22 @@ public: bool IsSavingEnabled() const { return saving_enabled_; } void SetSavingEnabled(bool e) { saving_enabled_ = e; } + virtual void SetPassthrough(PlaybackCache *cache); + QMutex *mutex() { return &mutex_; } + class Passthrough : public TimeRange + { + public: + Passthrough(const TimeRange &r) : + TimeRange(r) + {} + + QUuid cache; + }; + + const QVector &GetPassthroughs() const { return passthroughs_; } + public slots: void InvalidateAll(); @@ -116,6 +130,8 @@ private: QMutex mutex_; + QVector passthroughs_; + }; } diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 71f57b63c..615355ecc 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -698,8 +698,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) // Place the copy instead of the original block block = static_cast(Node::CopyNodeInGraph(block, command)); if (ClipBlock *new_clip = dynamic_cast(block)) { - qDebug() << "FIXME: Copy clip stub"; Q_UNUSED(new_clip) - //new_clip->set_waveform(static_cast(p.block)->waveform()); + new_clip->AddCachePassthroughFrom(static_cast(p.block)); } } diff --git a/app/widget/timelinewidget/undo/timelineundosplit.cpp b/app/widget/timelinewidget/undo/timelineundosplit.cpp index 3900d2b7b..da810f321 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.cpp +++ b/app/widget/timelinewidget/undo/timelineundosplit.cpp @@ -29,27 +29,20 @@ namespace olive { // // BlockSplitCommand // +void BlockSplitCommand::prepare() +{ + reconnect_tree_command_ = new MultiUndoCommand(); + new_block_ = static_cast(Node::CopyNodeInGraph(block_, reconnect_tree_command_)); +} + void BlockSplitCommand::redo() { old_length_ = block_->length(); Q_ASSERT(point_ > block_->in() && point_ < block_->out()); - if (!reconnect_tree_command_) { - reconnect_tree_command_ = new MultiUndoCommand(); - new_block_ = static_cast(Node::CopyNodeInGraph(block_, reconnect_tree_command_)); - } - reconnect_tree_command_->redo_now(); - if (ClipBlock *new_clip = dynamic_cast(new_block_)) { - ClipBlock *old_clip = static_cast(block_); - qDebug() << "FIXME: Copy waveform stub"; - Q_UNUSED(old_clip) - Q_UNUSED(new_clip) - //new_clip->set_waveform(old_clip->waveform()); - } - // Determine our new lengths rational new_length = point_ - block_->in(); rational new_part_length = block_->out() - point_; @@ -64,6 +57,11 @@ void BlockSplitCommand::redo() // Insert new block track->InsertBlockAfter(new_block(), block_); + if (ClipBlock *new_clip = dynamic_cast(new_block_)) { + ClipBlock *old_clip = static_cast(block_); + new_clip->AddCachePassthroughFrom(old_clip); + } + // If the block had an out transition, we move it to the new block moved_transition_ = NodeInput(); diff --git a/app/widget/timelinewidget/undo/timelineundosplit.h b/app/widget/timelinewidget/undo/timelineundosplit.h index 2cc12ff21..1fe9f9122 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.h +++ b/app/widget/timelinewidget/undo/timelineundosplit.h @@ -54,6 +54,8 @@ public: } protected: + virtual void prepare() override; + virtual void redo() override; virtual void undo() override; From 1b1259225e53bc1b5dca305c3332b552f5d60531 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 6 Jun 2022 20:06:55 -0700 Subject: [PATCH 21/53] render: handle missing cache frames --- app/render/previewautocacher.cpp | 15 ++++++++++++++- app/render/renderprocessor.cpp | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index b2cf4f4d2..b7ba2937d 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -26,6 +26,7 @@ #include "codec/conformmanager.h" #include "node/inputdragger.h" #include "node/project/project.h" +#include "render/diskmanager.h" #include "render/renderprocessor.h" #include "task/customcache/customcachetask.h" #include "task/taskmanager.h" @@ -201,7 +202,12 @@ void PreviewAutoCacher::VideoRendered() if (watcher->HasResult()) { if (watcher->GetTicket()->property("cached").toBool()) { if (FrameHashCache *cache = Node::ValueToPtr(watcher->property("cache"))) { - cache->ValidateTime(watcher->property("time").value()); + rational time = watcher->property("time").value(); + JobTime job = watcher->property("job").value(); + + if (video_cache_data_.value(cache).job_tracker.isCurrent(time, job)) { + cache->ValidateTime(time); + } } } } @@ -210,6 +216,13 @@ void PreviewAutoCacher::VideoRendered() TryRender(); } + const QStringList bad_cache_names = watcher->GetTicket()->property("badcache").toStringList(); + if (!bad_cache_names.empty()) { + for (const QString &fn : bad_cache_names) { + DiskManager::instance()->DeleteSpecificFile(fn); + } + } + // Process passthroughs no matter what, if the viewer was switched, the passthrough map would be // cleared anyway QVector tickets = video_immediate_passthroughs_.take(watcher); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 75ee420d2..2cdc10130 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -573,6 +573,10 @@ TexturePtr RenderProcessor::ProcessVideoCacheJob(const CacheJob &val) tex->Upload(frame->data(), frame->linesize_pixels()); return tex; } + } else { + QStringList s = ticket_->property("badcache").toStringList(); + s.append(val.GetFilename()); + ticket_->setProperty("badcache", s); } return nullptr; From e97c74fd2c45898eb5c420863c3220f4847db1cc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 8 Jun 2022 23:12:05 -0700 Subject: [PATCH 22/53] viewer: request new frames even when preprocessing --- app/widget/viewer/viewer.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 3f83d5ae7..e2f369ccc 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -771,8 +771,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) prequeue_count_ = 0; for (int i=0; iqueue()->AppendTimewise({ts, frame}, playback_speed_); } - prequeue_count_++; - if (prequeuing_video_ && prequeue_count_ == prequeue_length_) { - prequeuing_video_ = false; - FinishPlayPreprocess(); + if (prequeuing_video_) { + prequeue_count_++; + + if (prequeue_count_ == prequeue_length_) { + prequeuing_video_ = false; + FinishPlayPreprocess(); + } else { + RequestNextFrameForQueue(); + } } } } From 6716a7173b10d95fb0b65a2c7536721a5b2db53b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 2 Jul 2022 17:52:24 -0500 Subject: [PATCH 23/53] timeline: use consistent x offsets for thumbnails --- .../timelinewidget/view/timelineview.cpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 12434a91d..f5a2f83e4 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -527,22 +527,24 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q QRect thumb_rect; painter->setClipRect(preview_rect); painter->setRenderHint(QPainter::SmoothPixmapTransform); - for (int i=preview_rect.left(); itrack()->sequence(); + int width = s->GetVideoParams().width(); + int height = s->GetVideoParams().height(); + int start; + if (height > 0) { // Prevent divide by zero/invalid params + double scale = double(preview_rect.height())/double(height); + thumb_rect.setWidth(width * scale); + start = (preview_rect.left() - int(qFloor(block_in))) / thumb_rect.width() + qFloor(block_in); + } else { + start = preview_rect.left(); + } + + for (int i=start; iparent()->GetVideoParams().frame_rate_as_time_base()) + media_in; QString thumbnail = thumbs->GetValidCacheFilename(time_here); - if (thumbnail.isEmpty()) { - // Jump ahead to next frame, ensuring that frame width > 0 for optimization - if (thumb_rect.width() == 0 && clip->track() && clip->track()->sequence()) { - Sequence *s = clip->track()->sequence(); - int width = s->GetVideoParams().width(); - int height = s->GetVideoParams().height(); - if (height > 0) { // Prevent divide by zero/invalid params - double scale = double(preview_rect.height())/double(height); - thumb_rect.setWidth(width * scale); - } - } - } else { + if (!thumbnail.isEmpty()) { QImage img; if (img.load(thumbnail, "jpg")) { double scale = double(preview_rect.height())/double(img.height()); From b24cf3e6589f691e22b6d6a2e2303628bee4f44a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 18 Aug 2022 08:21:16 -0700 Subject: [PATCH 24/53] rendermanager: make declaring render mode mandatory --- app/render/previewautocacher.cpp | 8 ++++---- app/render/rendermanager.cpp | 1 + app/render/rendermanager.h | 8 ++++---- app/task/render/render.cpp | 8 +++----- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 16f1c6bab..587cd9553 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -671,7 +671,8 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& copied_viewer_node_->GetVideoParams(), copied_viewer_node_->GetAudioParams(), time, - copied_color_manager_); + copied_color_manager_, + RenderMode::kOffline); if (FrameHashCache *frame_cache = dynamic_cast(cache)) { if (ThumbnailCache *wave_cache = dynamic_cast(cache)) { @@ -687,7 +688,6 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& rvp.AddCache(frame_cache); } - rvp.mode = RenderMode::kOffline; rvp.return_type = dry ? RenderManager::kNull : RenderManager::kTexture; // Allow using cached images for this render job @@ -710,11 +710,11 @@ RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, P RenderManager::RenderAudioParams rap(node, r, - copied_viewer_node_->GetAudioParams()); + copied_viewer_node_->GetAudioParams(), + RenderMode::kOffline); rap.generate_waveforms = dynamic_cast(cache); rap.clamp = false; - rap.mode = RenderMode::kOffline; RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(rap); watcher->SetTicket(ticket); diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 7ea8ecfd1..f8f43d1be 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -131,6 +131,7 @@ RenderTicketPtr RenderManager::RenderAudio(const RenderAudioParams ¶ms) ticket->setProperty("enablewaveforms", params.generate_waveforms); ticket->setProperty("clamp", params.clamp); ticket->setProperty("aparam", QVariant::fromValue(params.audio_params)); + ticket->setProperty("mode", params.mode); audio_thread_->AddTicket(ticket); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 877ca26b5..e16cb5bd9 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -103,7 +103,7 @@ public: struct RenderVideoParams { RenderVideoParams(Node *n, const VideoParams &vparam, const AudioParams &aparam, const rational &t, - ColorManager *colorman) + ColorManager *colorman, RenderMode::Mode m) { node = n; video_params = vparam; @@ -116,7 +116,7 @@ public: force_color_output = nullptr; force_size = QSize(0, 0); force_channel_count = 0; - mode = RenderMode::kOffline; + mode = m; } void AddCache(FrameHashCache *cache) @@ -159,14 +159,14 @@ public: RenderTicketPtr RenderFrame(const RenderVideoParams ¶ms); struct RenderAudioParams { - RenderAudioParams(Node *n, const TimeRange &time, const AudioParams &aparam) + RenderAudioParams(Node *n, const TimeRange &time, const AudioParams &aparam, RenderMode::Mode m) { node = n; range = time; audio_params = aparam; generate_waveforms = false; clamp = true; - mode = RenderMode::kOffline; + mode = m; } Node *node; diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index f1caab712..b3bfca484 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -62,9 +62,8 @@ bool RenderTask::Render(ColorManager* manager, RenderManager::RenderAudioParams rap(viewer_->GetConnectedSampleOutput(), range, - audio_params_); - - rap.mode = RenderMode::kOnline; + audio_params_, + RenderMode::kOnline); RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("range", QVariant::fromValue(range)); @@ -284,14 +283,13 @@ void RenderTask::StartTicket(QThread* watcher_thread, ColorManager* manager, ColorProcessorPtr force_color_output) { RenderManager::RenderVideoParams rvp(viewer_->GetConnectedTextureOutput(), video_params_, audio_params_, - time, manager); + time, manager, mode); rvp.force_size = force_size; rvp.force_matrix = force_matrix; rvp.force_format = force_format; rvp.force_color_output = force_color_output; rvp.force_channel_count = force_channel_count; - rvp.mode = mode; if (cache) { rvp.AddCache(cache); From 53def87749ea008807349cea97b237d9964f3f2f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:05:30 -0700 Subject: [PATCH 25/53] timeline: request caches on import --- app/node/block/clip/clip.h | 2 ++ app/widget/timelinewidget/tool/import.cpp | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 57b1daa4f..9176341eb 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -73,6 +73,8 @@ public: virtual void Retranslate() override; + void RerequestCaches() { RequestInvalidatedFromConnected(); } + double speed() const { return GetStandardValue(kSpeedInput).toDouble(); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 3914e137f..73a100bff 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -401,6 +401,8 @@ void ImportTool::DropGhosts(bool insert) } } + std::list imported_clips; + if (dst_graph) { QVector block_items(parent()->GetGhostItems().size()); @@ -471,6 +473,8 @@ void ImportTool::DropGhosts(bool insert) Block::Link(block_items.at(j), clip); } } + + imported_clips.push_back(clip); } else if (track_type == Track::kSubtitle) { Subtitle src = ghost->GetData(TimelineViewGhostItem::kAttachedFootage).value(); SubtitleBlock *sub = new SubtitleBlock(); @@ -498,6 +502,11 @@ void ImportTool::DropGhosts(bool insert) Core::instance()->undo_stack()->pushIfHasChildren(command); + while (!imported_clips.empty()) { + imported_clips.front()->RerequestCaches(); + imported_clips.pop_front(); + } + parent()->ClearGhosts(); dragged_footage_.clear(); } From 877c967ce24e0197a6db1d6853cca9fa7c05be46 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:05:46 -0700 Subject: [PATCH 26/53] rendermanager: move ticket to render thread --- app/render/rendermanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index f8f43d1be..cb3db2390 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -196,6 +196,7 @@ RenderThread::RenderThread(Renderer *renderer, DecoderCache *decoder_cache, Shad void RenderThread::AddTicket(RenderTicketPtr ticket) { QMutexLocker locker(&mutex_); + ticket->moveToThread(this); queue_.push_back(ticket); wait_.wakeOne(); } From 4f53869b989da6cf2f71c64feb1db56563cc1403 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 1 Sep 2022 12:12:15 -0700 Subject: [PATCH 27/53] samplebuffer: use std::vector and size_t for all calculations --- app/codec/samplebuffer.cpp | 48 +++++++++++++++----------------------- app/codec/samplebuffer.h | 32 ++++++++++++------------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/app/codec/samplebuffer.cpp b/app/codec/samplebuffer.cpp index fd4dfa5a7..c5a746387 100644 --- a/app/codec/samplebuffer.cpp +++ b/app/codec/samplebuffer.cpp @@ -36,7 +36,7 @@ SampleBuffer::SampleBuffer(const AudioParams &audio_params, const rational &leng allocate(); } -SampleBuffer::SampleBuffer(const AudioParams &audio_params, int samples_per_channel) : +SampleBuffer::SampleBuffer(const AudioParams &audio_params, size_t samples_per_channel) : audio_params_(audio_params), sample_count_per_channel_(samples_per_channel) { @@ -58,12 +58,7 @@ void SampleBuffer::set_audio_params(const AudioParams ¶ms) audio_params_ = params; } -const int &SampleBuffer::sample_count() const -{ - return sample_count_per_channel_; -} - -void SampleBuffer::set_sample_count(const int &sample_count) +void SampleBuffer::set_sample_count(const size_t &sample_count) { if (is_allocated()) { qWarning() << "Tried to set sample count on allocated sample buffer"; @@ -73,11 +68,6 @@ void SampleBuffer::set_sample_count(const int &sample_count) sample_count_per_channel_ = sample_count; } -bool SampleBuffer::is_allocated() const -{ - return !data_.isEmpty(); -} - void SampleBuffer::allocate() { if (!audio_params_.is_valid()) { @@ -113,10 +103,10 @@ void SampleBuffer::reverse() return; } - int half_nb_sample = sample_count_per_channel_ / 2; + size_t half_nb_sample = sample_count_per_channel_ / 2; - for (int i=0;i(sample_count_per_channel_) / speed); - QVector< QVector > output_data; + std::vector< std::vector > output_data; output_data.resize(audio_params_.channel_count()); for (int i=0; i(i) * speed); + for (size_t i=0;i(i) * speed); for (int j=0;j to_raw_ptrs() + std::vector to_raw_ptrs() { - QVector r(data_.size()); - for (int i=0; i r(data_.size()); + for (size_t i=0; i > data_; + std::vector< std::vector > data_; }; From 21231540fd2e6bca3aa6d6dc0e0698454d1d8c7a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 1 Sep 2022 12:52:23 -0700 Subject: [PATCH 28/53] audio: use std vectors and size_t for sample indices --- app/audio/audiovisualwaveform.cpp | 116 +++++++++++++++--------------- app/audio/audiovisualwaveform.h | 16 ++--- app/render/audiowaveformcache.cpp | 2 +- app/render/renderprocessor.cpp | 6 +- 4 files changed, 72 insertions(+), 68 deletions(-) diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 58aabc788..14f347b94 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -39,50 +39,50 @@ AudioVisualWaveform::AudioVisualWaveform() : } } -void AudioVisualWaveform::OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational &start, double target_rate, Sample& data, int &start_index, int &samples_length) +void AudioVisualWaveform::OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational &start, double target_rate, Sample& data, size_t &start_index, size_t &samples_length) { start_index = time_to_samples(start, target_rate); samples_length = time_to_samples(static_cast(samples.sample_count()) / static_cast(sample_rate), target_rate); - int end_index = start_index + samples_length; + size_t end_index = start_index + samples_length; if (data.size() < end_index) { data.resize(end_index); } double chunk_size = double(sample_rate) / double(target_rate); - for (int i=0; i(input_length / channels_) / input_sample_rate, output_rate); + size_t start_index = time_to_samples(start, output_rate); + size_t samples_length = time_to_samples(static_cast(input_length / channels_) / input_sample_rate, output_rate); - int end_index = start_index + samples_length; + size_t end_index = start_index + samples_length; if (output_data.size() < end_index) { output_data.resize(end_index); } // We guarantee mipmaps are powers of two so integer division should be perfectly accurate here - int chunk_size = input_sample_rate / output_rate; + size_t chunk_size = input_sample_rate / output_rate; - for (int i=0; ifirst.toDouble(), it->second, input_start, input_length); // } // Process the largest mipmap directly for the samples auto current_mipmap = mipmapped_data_.rbegin(); - int input_start, input_length; + size_t input_start, input_length; OverwriteSamplesFromBuffer(samples, sample_rate, start, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length); while (true) { @@ -139,16 +139,16 @@ void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const r double rate_dbl = rate.toDouble(); // Get our destination sample - int our_start_index = time_to_samples(dest, rate_dbl); + size_t our_start_index = time_to_samples(dest, rate_dbl); // Get our source sample - int their_start_index = time_to_samples(offset, rate_dbl); + size_t their_start_index = time_to_samples(offset, rate_dbl); if (their_start_index >= their_arr.size()) { continue; } // Determine how much we're copying - int copy_len = their_arr.size() - their_start_index; + size_t copy_len = their_arr.size() - their_start_index; if (!length.isNull()) { copy_len = qMin(copy_len, time_to_samples(length, rate_dbl)); if (copy_len == 0) { @@ -157,13 +157,13 @@ void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const r } // Determine end index of our array - int end_index = our_start_index + copy_len; + size_t end_index = our_start_index + copy_len; if (our_arr.size() < end_index) { our_arr.resize(end_index); } memcpy(reinterpret_cast(our_arr.data()) + our_start_index * sizeof(SamplePerChannel), - reinterpret_cast(their_arr.constData()) + their_start_index * sizeof(SamplePerChannel), + reinterpret_cast(their_arr.data()) + their_start_index * sizeof(SamplePerChannel), copy_len * sizeof(SamplePerChannel)); } @@ -180,9 +180,9 @@ void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational double rate_dbl = rate.toDouble(); // Get our destination sample - int our_start_index = time_to_samples(start, rate_dbl); - int our_length_index = time_to_samples(length, rate_dbl); - int our_end_index = our_start_index + our_length_index; + size_t our_start_index = time_to_samples(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; if (our_arr.size() < our_end_index) { our_arr.resize(our_end_index); @@ -192,27 +192,31 @@ void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational } } -void AudioVisualWaveform::TrimIn(const rational &length) +void AudioVisualWaveform::TrimIn(rational length) { if (length == 0) { return; } + bool negative = (length < 0); + if (negative) { + length = -length; + } + for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) { rational rate = it->first; double rate_dbl = rate.toDouble(); Sample& data = it->second; - int chop_length = time_to_samples(length, rate_dbl); - + size_t chop_length = time_to_samples(length, rate_dbl); if (chop_length == 0) { continue; } - if (chop_length > 0) { - data = data.mid(chop_length); + if (!negative) { + data = Sample(data.begin() + chop_length, data.end()); } else { - data.insert(0, -chop_length, SamplePerChannel()); + data.insert(data.begin(), chop_length, SamplePerChannel()); } } @@ -248,7 +252,7 @@ void AudioVisualWaveform::Resize(const rational &length) double rate_dbl = rate.toDouble(); Sample& data = it->second; - int chop_length = time_to_samples(length, rate_dbl); + size_t chop_length = time_to_samples(length, rate_dbl); data.resize(chop_length); } @@ -269,10 +273,10 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration double rate_dbl = using_mipmap->first.toDouble(); - int start_sample = time_to_samples(start, rate_dbl); - int sample_length = time_to_samples(length, rate_dbl); + size_t start_sample = time_to_samples(start, rate_dbl); + size_t sample_length = time_to_samples(length, rate_dbl); - const QVector &mipmap_data = using_mipmap->second; + const Sample &mipmap_data = using_mipmap->second; // Determine if the array actually has this sample sample_length = qMin(sample_length, mipmap_data.size() - start_sample); @@ -280,14 +284,14 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration // Based on the above `min`, if sample length <= 0, that means start_sample >= the size of the // array and nothing can be returned. if (sample_length > 0) { - return ReSumSamples(&mipmap_data.constData()[start_sample], sample_length, channels_); + return ReSumSamples(&mipmap_data.data()[start_sample], sample_length, channels_); } // Return null samples return AudioVisualWaveform::Sample(channel_count(), {0, 0}); } -void ExpandMinMaxChannel(const float *a, int start, int length, float &min_val, float &max_val) +void ExpandMinMaxChannel(const float *a, size_t start, size_t length, float &min_val, float &max_val) { #if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM) // SSE optimized @@ -298,7 +302,7 @@ void ExpandMinMaxChannel(const float *a, int start, int length, float &min_val, // loop over 'a' and compare current elements with min and max 4 by 4. // we need to make sure we don't read out of boundaries should 'a' length be not mod. 4 - for(int i = 4; i < length-4; i+=4) { + for(size_t i = 4; i < length-4; i+=4) { __m128 cur = _mm_loadu_ps(a + start + i); max = _mm_max_ps(max, cur); min = _mm_min_ps(min, cur); @@ -311,7 +315,7 @@ void ExpandMinMaxChannel(const float *a, int start, int length, float &min_val, // min and max will contain 4 min and max. To get the absolute min and max // we need to compare the 4 values over themselves by shuffling each time. - for (int i = 0; i < 3; i++) { + for (size_t i = 0; i < 3; i++) { max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93)); min = _mm_min_ps(min, _mm_shuffle_ps(min, min, 0x93)); } @@ -323,15 +327,15 @@ void ExpandMinMaxChannel(const float *a, int start, int length, float &min_val, // I bet you don't find annotated low level code very often. #else // Standard unoptimized function - int end = start + length; - for (int i=start; idata(i%channels)[i]); // } @@ -349,12 +353,12 @@ AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const SampleBuffer & } AudioVisualWaveform::Sample AudioVisualWaveform::ReSumSamples(const SamplePerChannel* samples, - int nb_samples, + size_t nb_samples, int nb_channels) { AudioVisualWaveform::Sample summed_samples(nb_channels); - for (int i=0;isecond; - int start_sample_index = samples.time_to_samples(start_time, rate_dbl); + size_t start_sample_index = samples.time_to_samples(start_time, rate_dbl); if (start_sample_index >= arr.size()) { return; } - int next_sample_index = start_sample_index; - int sample_index; + size_t next_sample_index = start_sample_index; + size_t sample_index; Sample summary; - int summary_index = -1; + size_t summary_index = -1; const QRect& viewport = painter->viewport(); QPoint top_left = painter->transform().map(viewport.topLeft()); - int start = qMax(rect.x(), -top_left.x()); - int end = qMin(rect.right(), -top_left.x() + viewport.width()); + size_t start = qMax(rect.x(), -top_left.x()); + size_t end = qMin(rect.right(), -top_left.x() + viewport.width()); bool rectified = OLIVE_CONFIG("RectifiedWaveforms").toBool(); - for (int i=start;i; + using Sample = std::vector; int channel_count() const { @@ -90,7 +90,7 @@ public: void OverwriteSilence(const rational &start, const rational &length); - void TrimIn(const rational &length); + void TrimIn(rational length); AudioVisualWaveform Mid(const rational &offset) const; AudioVisualWaveform Mid(const rational &offset, const rational &length) const; @@ -101,9 +101,9 @@ public: Sample GetSummaryFromTime(const rational& start, const rational& length) const; - static Sample SumSamples(const SampleBuffer &samples, int start_index, int length); + static Sample SumSamples(const SampleBuffer &samples, size_t start_index, size_t length); - static Sample ReSumSamples(const SamplePerChannel *samples, int nb_samples, int nb_channels); + static Sample ReSumSamples(const SamplePerChannel *samples, size_t nb_samples, int nb_channels); static void DrawSample(QPainter* painter, const Sample &sample, int x, int y, int height, bool rectified); @@ -114,12 +114,12 @@ public: static const rational kMaximumSampleRate; private: - void OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational& start, double target_rate, Sample &data, int &start_index, int &samples_length); + void OverwriteSamplesFromBuffer(const SampleBuffer &samples, int sample_rate, const rational& start, double target_rate, Sample &data, size_t &start_index, size_t &samples_length); - void OverwriteSamplesFromMipmap(const Sample& input, double input_sample_rate, int &input_start, int &input_length, const rational& start, double output_rate, Sample &output_data); + void OverwriteSamplesFromMipmap(const Sample& input, double input_sample_rate, size_t &input_start, size_t &input_length, const rational& start, double output_rate, Sample &output_data); - int time_to_samples(const rational& time, double sample_rate) const; - int time_to_samples(const double& time, double sample_rate) const; + size_t time_to_samples(const rational& time, double sample_rate) const; + size_t time_to_samples(const double& time, double sample_rate) const; std::map::const_iterator GetMipmapForScale(double scale) const; diff --git a/app/render/audiowaveformcache.cpp b/app/render/audiowaveformcache.cpp index 5b4898f7d..b902b1957 100644 --- a/app/render/audiowaveformcache.cpp +++ b/app/render/audiowaveformcache.cpp @@ -81,7 +81,7 @@ AudioVisualWaveform::Sample AudioWaveformCache::GetSummaryFromTime(const rationa AudioVisualWaveform::Sample result; for (auto it=sample.cbegin(); it!=sample.cend(); it++) { - result.append(it.value()); + result.insert(result.end(), it.value().begin(), it.value().end()); } return result; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 0766ac606..1e69ea2ce 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -315,8 +315,8 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim TimeRange range_for_block(qMax(b->in(), range.in()), qMin(b->out(), range.out())); - int destination_offset = audio_params.time_to_samples(range_for_block.in() - range.in()); - int max_dest_sz = audio_params.time_to_samples(range_for_block.length()); + 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)); @@ -380,7 +380,7 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim } } - int copy_length = qMin(max_dest_sz, samples_from_this_block.sample_count()); + qint64 copy_length = qMin(max_dest_sz, qint64(samples_from_this_block.sample_count())); // Copy samples into destination buffer for (int i=0; i Date: Tue, 6 Sep 2022 16:06:26 -0700 Subject: [PATCH 29/53] timeline: added thumbnail modes that affect how much is rendered --- app/common/timerange.cpp | 7 +- app/common/timerange.h | 2 + app/config/config.cpp | 4 + app/node/block/clip/clip.cpp | 66 +++++++++++++--- app/node/block/clip/clip.h | 2 + app/render/framehashcache.h | 1 + app/render/previewautocacher.cpp | 14 +++- app/timeline/timelinecommon.h | 11 +++ app/widget/timelinewidget/timelinewidget.cpp | 28 ++++--- app/widget/timelinewidget/timelinewidget.h | 2 +- .../timelinewidget/view/timelineview.cpp | 76 ++++++++++++------- app/widget/timelinewidget/view/timelineview.h | 27 +------ 12 files changed, 159 insertions(+), 81 deletions(-) diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index 7326794ef..b79555937 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -312,6 +312,11 @@ TimeRangeListFrameIterator::TimeRangeListFrameIterator(const TimeRangeList &list UpdateIndexIfNecessary(); } +rational TimeRangeListFrameIterator::Snap(const rational &r) const +{ + return Timecode::snap_time_to_timebase(r, timebase_, Timecode::kFloor); +} + bool TimeRangeListFrameIterator::GetNext(rational *out) { if (!HasNext()) { @@ -368,7 +373,7 @@ void TimeRangeListFrameIterator::UpdateIndexIfNecessary() range_index_++; if (range_index_ < list_.size()) { - current_ = Timecode::snap_time_to_timebase(list_.at(range_index_).in(), timebase_, Timecode::kCeil); + current_ = Snap(list_.at(range_index_).in()); } } } diff --git a/app/common/timerange.h b/app/common/timerange.h index 882b14d36..b6235f9b8 100644 --- a/app/common/timerange.h +++ b/app/common/timerange.h @@ -207,6 +207,8 @@ public: TimeRangeListFrameIterator(); TimeRangeListFrameIterator(const TimeRangeList &list, const rational &timebase); + rational Snap(const rational &r) const; + bool GetNext(rational *out); bool HasNext() const; diff --git a/app/config/config.cpp b/app/config/config.cpp index 1e0ee70a2..ab2bbac5e 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -32,6 +32,7 @@ #include "common/filefunctions.h" #include "common/xmlutils.h" #include "core.h" +#include "timeline/timelinecommon.h" #include "ui/colorcoding.h" #include "ui/style/style.h" #include "window/mainwindow/mainwindow.h" @@ -104,6 +105,9 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("TimelineThumbnailMode"), NodeValue::kInt, Timeline::kThumbnailOn); + SetEntryInternal(QStringLiteral("TimelineWaveformMode"), NodeValue::kInt, Timeline::kWaveformsEnabled); + SetEntryInternal(QStringLiteral("DefaultVideoTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); SetEntryInternal(QStringLiteral("DefaultAudioTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); SetEntryInternal(QStringLiteral("DefaultTransitionLength"), NodeValue::kRational, QVariant::fromValue(rational(1))); diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 201f37c43..08c43f86b 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -20,6 +20,7 @@ #include "clip.h" +#include "config/config.h" #include "node/output/track/track.h" #include "node/output/viewer/viewer.h" #include "widget/slider/floatslider.h" @@ -108,12 +109,14 @@ void ClipBlock::set_length_and_media_in(const rational &length) return; } - if (!reverse()) { - // Calculate media_in adjustment - set_media_in(SequenceToMediaTime(this->length() - length, kSTMIgnoreLoop)); - } + rational old_length = this->length(); super::set_length_and_media_in(length); + + if (!reverse()) { + // Calculate media_in adjustment + set_media_in(SequenceToMediaTime(old_length - length, kSTMIgnoreLoop)); + } } rational ClipBlock::media_in() const @@ -124,6 +127,8 @@ rational ClipBlock::media_in() const void ClipBlock::set_media_in(const rational &media_in) { SetStandardValue(kMediaInInput, QVariant::fromValue(media_in)); + + RequestInvalidatedFromConnected(); } void ClipBlock::SetAutocache(bool e) @@ -208,16 +213,22 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range) if (type == Track::kVideo || type == Track::kAudio) { if (Node *connected = GetConnectedOutput(kBufferIn)) { - TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); + TimeRange max_range = media_range(); if (type == Track::kVideo) { // Handle thumbnails - RequestRangeForCache(connected->thumbnail_cache(), max_range, range, true, true); + RequestRangeForCache(connected->thumbnail_cache(), max_range, range, true, false); + { + TimeRange thumb_range = range; + if (GetAdjustedThumbnailRange(&thumb_range)) { + emit connected->thumbnail_cache()->Request(thumb_range); + } + } // Handle video cache RequestRangeForCache(connected->video_frame_cache(), max_range, range, true, IsAutocaching()); } else if (type == Track::kAudio) { // Handle waveforms - RequestRangeForCache(connected->waveform_cache(), max_range, range, true, true); + RequestRangeForCache(connected->waveform_cache(), max_range, range, true, (OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled)); // Handle audio cache RequestRangeForCache(connected->audio_playback_cache(), max_range, range, true, IsAutocaching()); @@ -232,10 +243,13 @@ void ClipBlock::RequestInvalidatedFromConnected() if (type == Track::kVideo || type == Track::kAudio) { if (Node *connected = GetConnectedOutput(kBufferIn)) { - TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); + TimeRange max_range = media_range(); if (type == Track::kVideo) { // Handle thumbnails - RequestInvalidatedForCache(connected->thumbnail_cache(), max_range); + TimeRange thumb_range = max_range; + if (GetAdjustedThumbnailRange(&thumb_range)) { + RequestInvalidatedForCache(connected->thumbnail_cache(), thumb_range); + } // Handle video cache if (IsAutocaching()) { @@ -243,7 +257,9 @@ void ClipBlock::RequestInvalidatedFromConnected() } } else if (type == Track::kAudio) { // Handle waveforms - RequestInvalidatedForCache(connected->waveform_cache(), max_range); + if (OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled) { + RequestInvalidatedForCache(connected->waveform_cache(), max_range); + } // Handle audio cache if (IsAutocaching()) { @@ -280,6 +296,34 @@ void ClipBlock::RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange } } +bool ClipBlock::GetAdjustedThumbnailRange(TimeRange *r) const +{ + switch (static_cast(OLIVE_CONFIG("TimelineThumbnailMode").toInt())) { + case Timeline::kThumbnailOff: + // Don't cache any range + return false; + case Timeline::kThumbnailInOut: + { + // Only cache in point + rational in = this->media_range().in(); + if (r->Contains(in)) { + // Cache only the in point + *r = TimeRange(in, in + thumbnail_cache()->GetTimebase()); + return true; + } else { + // Cache nothing + return false; + } + } + case Timeline::kThumbnailOn: + // Cache entire range + return true; + } + + // Fallback + return true; +} + void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) { Q_UNUSED(element) @@ -379,8 +423,10 @@ void ClipBlock::InputValueChangedEvent(const QString &input, int element) if (Node *connected = GetConnectedOutput(kBufferIn)) { if (type == Track::kVideo) { emit connected->video_frame_cache()->CancelAll(); + //emit connected->thumbnail_cache()->CancelAll(); } else if (type == Track::kAudio) { emit connected->audio_playback_cache()->CancelAll(); + //emit connected->waveform_cache()->CancelAll(); } } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 9176341eb..8f98ee80f 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -232,6 +232,8 @@ private: void RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range, bool invalidate, bool request); void RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range); + bool GetAdjustedThumbnailRange(TimeRange *r) const; + QVector block_links_; TransitionBlock* in_transition_; diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index b98460c49..378e1a02a 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -93,6 +93,7 @@ public: ThumbnailCache(QObject* parent = nullptr) : FrameHashCache(parent) { + SetTimebase(rational(1, 10)); } }; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 587cd9553..beb94aeb2 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -459,8 +459,16 @@ void PreviewAutoCacher::StartCachingRange(const TimeRange &range, TimeRangeList void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeRange &range) { Node *node = cache->parent(); - pending_video_jobs_.push_back({node, cache, range, TimeRangeListFrameIterator({range}, viewer_node_->GetVideoParams().frame_rate_as_time_base())}); - video_cache_data_[cache].job_tracker.insert(range, graph_changed_time_); + rational using_tb; + if (ThumbnailCache *thumbs = dynamic_cast(cache)) { + using_tb = thumbs->GetTimebase(); + } else { + using_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base(); + } + + 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_); TryRender(); } @@ -679,8 +687,6 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& rvp.video_params.set_divider(VideoParams::GetDividerForTargetResolution(rvp.video_params.width(), rvp.video_params.height(), 160, 120)); rvp.force_color_output = display_color_processor_; rvp.force_format = VideoParams::kFormatUnsigned8; - - wave_cache->SetTimebase(rational(1, 10)); } else { frame_cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); } diff --git a/app/timeline/timelinecommon.h b/app/timeline/timelinecommon.h index 5c6d96df9..cdc513149 100644 --- a/app/timeline/timelinecommon.h +++ b/app/timeline/timelinecommon.h @@ -38,6 +38,17 @@ public: kTrimOut }; + enum ThumbnailMode { + kThumbnailOff, + kThumbnailInOut, + kThumbnailOn + }; + + enum WaveformMode { + kWaveformsDisabled, + kWaveformsEnabled + }; + static bool IsATrimMode(MovementMode mode) {return mode == kTrimIn || mode == kTrimOut;} struct EditToInfo { diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 730560b9c..44e63c687 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1140,14 +1140,20 @@ void TimelineWidget::ShowContextMenu() toggle_audio_units->setChecked(use_audio_time_units_); connect(toggle_audio_units, &QAction::triggered, this, &TimelineWidget::SetUseAudioTimeUnits); - QAction* show_thumbnails = menu.addAction(tr("Show Thumbnails")); - show_thumbnails->setCheckable(true); - show_thumbnails->setChecked(views_.first()->view()->GetShowThumbnails()); - connect(show_thumbnails, &QAction::triggered, this, &TimelineWidget::SetViewThumbnailsEnabled); + { + Menu *thumbnail_menu = new Menu(tr("Show Thumbnails"), &menu); + menu.addMenu(thumbnail_menu); + + thumbnail_menu->AddActionWithData(tr("Disabled"), Timeline::kThumbnailOff, OLIVE_CONFIG("TimelineThumbnailMode")); + thumbnail_menu->AddActionWithData(tr("Only At In/Out Points"), Timeline::kThumbnailInOut, OLIVE_CONFIG("TimelineThumbnailMode")); + thumbnail_menu->AddActionWithData(tr("Enabled"), Timeline::kThumbnailOn, OLIVE_CONFIG("TimelineThumbnailMode")); + + connect(thumbnail_menu, &Menu::triggered, this, &TimelineWidget::SetViewThumbnailsEnabled); + } QAction* show_waveforms = menu.addAction(tr("Show Waveforms")); show_waveforms->setCheckable(true); - show_waveforms->setChecked(views_.first()->view()->GetShowWaveforms()); + show_waveforms->setChecked(OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled); connect(show_waveforms, &QAction::triggered, this, &TimelineWidget::SetViewWaveformsEnabled); menu.addSeparator(); @@ -1211,16 +1217,14 @@ void TimelineWidget::AddableObjectChanged() void TimelineWidget::SetViewWaveformsEnabled(bool e) { - foreach (TimelineAndTrackView* tview, views_) { - tview->view()->SetShowWaveforms(e); - } + OLIVE_CONFIG("TimelineWaveformMode") = e ? Timeline::kWaveformsEnabled : Timeline::kWaveformsDisabled; + UpdateViewports(); } -void TimelineWidget::SetViewThumbnailsEnabled(bool e) +void TimelineWidget::SetViewThumbnailsEnabled(QAction *action) { - foreach (TimelineAndTrackView* tview, views_) { - tview->view()->SetShowThumbnails(e); - } + OLIVE_CONFIG("TimelineThumbnailMode") = action->data(); + UpdateViewports(); } void TimelineWidget::FrameRateChanged() diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 138dc39d4..b2f869443 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -423,7 +423,7 @@ private slots: void SetViewWaveformsEnabled(bool e); - void SetViewThumbnailsEnabled(bool e); + void SetViewThumbnailsEnabled(QAction *action); void FrameRateChanged(); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index bcb9fe2c2..555d3bc62 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -46,8 +46,6 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : ghosts_(nullptr), show_beam_cursor_(false), connected_track_list_(nullptr), - show_thumbnails_(true), - show_waveforms_(true), transition_overlay_out_(nullptr), transition_overlay_in_(nullptr) { @@ -486,7 +484,6 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q int text_height = fm.height(); int text_padding = text_height/4; // This ties into the track minimum height being 1.5 int text_total_height = text_height + text_padding + text_padding; - Q_UNUSED(text_total_height) if (foreground) { painter->setBrush(Qt::NoBrush); @@ -524,43 +521,52 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q QRect preview_rect = r.toRect(); // Draw clip thumbnails - if (clip->GetTrackType() == Track::kVideo && show_thumbnails_ && preview_rect.height() > r.height()/3) { + if (clip->GetTrackType() == Track::kVideo + && OLIVE_CONFIG("TimelineThumbnailMode").toInt() != Timeline::kThumbnailOff + && preview_rect.height() > r.height()/3) { if (const FrameHashCache *thumbs = clip->thumbnails()) { + // Start thumbnails underneath clip name + preview_rect.adjust(0, text_total_height, 0, 0); + QRect thumb_rect; - painter->setClipRect(preview_rect); painter->setRenderHint(QPainter::SmoothPixmapTransform); + painter->setClipRect(preview_rect); - Sequence *s = clip->track()->sequence(); - int width = s->GetVideoParams().width(); - int height = s->GetVideoParams().height(); - int start; - if (height > 0) { // Prevent divide by zero/invalid params - double scale = double(preview_rect.height())/double(height); - thumb_rect.setWidth(width * scale); - start = (preview_rect.left() - int(qFloor(block_in))) / thumb_rect.width() + qFloor(block_in); - } else { - start = preview_rect.left(); - } + if (OLIVE_CONFIG("TimelineThumbnailMode") == Timeline::kThumbnailOn) { - for (int i=start; iparent()->GetVideoParams().frame_rate_as_time_base()) + media_in; - QString thumbnail = thumbs->GetValidCacheFilename(time_here); - - if (!thumbnail.isEmpty()) { - QImage img; - if (img.load(thumbnail, "jpg")) { - double scale = double(preview_rect.height())/double(img.height()); - thumb_rect = QRect(i, preview_rect.top(), img.width() * scale, preview_rect.height()); - painter->drawImage(thumb_rect, img); - } + Sequence *s = clip->track()->sequence(); + int width = s->GetVideoParams().width(); + int height = s->GetVideoParams().height(); + int start; + if (height > 0) { // Prevent divide by zero/invalid params + double scale = double(preview_rect.height())/double(height); + thumb_rect.setWidth(width * scale); + start = (((preview_rect.left() - int(qFloor(block_in))) / thumb_rect.width()) * thumb_rect.width()) + qFloor(block_in); + } else { + start = preview_rect.left(); } + + for (int i=start; iparent()->GetVideoParams().frame_rate_as_time_base()) + media_in; + DrawThumbnail(painter, thumbs, time_here, i, preview_rect, &thumb_rect); + } + + } else { + + rational time = clip->media_range().in(); + time = Timecode::snap_time_to_timebase(time, thumbs->GetTimebase(), Timecode::kFloor); + DrawThumbnail(painter, thumbs, time, block_left, preview_rect, &thumb_rect); + } + painter->setClipping(false); + } } // Draw waveform - if (clip->GetTrackType() == Track::kAudio && show_waveforms_) { + if (clip->GetTrackType() == Track::kAudio + && OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled) { if (const AudioWaveformCache *wave = clip->waveform()) { rational waveform_start = SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in; painter->setPen(shadow_color); @@ -733,6 +739,20 @@ qreal TimelineView::GetTimelineRightBound() const return GetTimelineLeftBound() + viewport()->width(); } +void TimelineView::DrawThumbnail(QPainter *painter, const FrameHashCache *thumbs, const rational &time, int x, const QRect &preview_rect, QRect *thumb_rect) const +{ + QString thumbnail = thumbs->GetValidCacheFilename(time); + + if (!thumbnail.isEmpty()) { + QImage img; + if (img.load(thumbnail, "jpg")) { + double scale = double(preview_rect.height())/double(img.height()); + *thumb_rect = QRect(x, preview_rect.top(), img.width() * scale, preview_rect.height()); + painter->drawImage(*thumb_rect, img); + } + } +} + int TimelineView::GetTrackY(int track_index) const { if (!connected_track_list_ || !connected_track_list_->GetTrackCount()) { diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 9dea755f2..6ce72ca01 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -73,28 +73,6 @@ public: Block* GetItemAtScenePos(const rational& time, int track_index) const; - bool GetShowWaveforms() const - { - return show_waveforms_; - } - - void SetShowWaveforms(bool e) - { - show_waveforms_ = e; - viewport()->update(); - } - - bool GetShowThumbnails() const - { - return show_thumbnails_; - } - - void SetShowThumbnails(bool e) - { - show_thumbnails_ = e; - viewport()->update(); - } - signals: void MousePressed(TimelineViewMouseEvent* event); void MouseMoved(TimelineViewMouseEvent* event); @@ -154,6 +132,8 @@ private: qreal GetTimelineRightBound() const; + void DrawThumbnail(QPainter *painter, const FrameHashCache *thumbs, const rational &time, int x, const QRect &preview_rect, QRect *thumb_rect) const; + QHash* selections_; QVector* ghosts_; @@ -164,9 +144,6 @@ private: TrackList* connected_track_list_; - bool show_thumbnails_; - bool show_waveforms_; - ClipBlock *transition_overlay_out_; ClipBlock *transition_overlay_in_; From 97ae714017959b4afc61496d5e79bb477cc4e0e5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 6 Sep 2022 17:00:21 -0700 Subject: [PATCH 30/53] clip: fixed issue where thumbs would be cached infinitely --- app/node/block/clip/clip.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 08c43f86b..c898066ed 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -218,7 +218,7 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range) // Handle thumbnails RequestRangeForCache(connected->thumbnail_cache(), max_range, range, true, false); { - TimeRange thumb_range = range; + TimeRange thumb_range = range.Intersected(max_range); if (GetAdjustedThumbnailRange(&thumb_range)) { emit connected->thumbnail_cache()->Request(thumb_range); } From 84fa33e510ce9758974c3cb146b1ddd784dde6b2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 8 Sep 2022 20:28:48 -0700 Subject: [PATCH 31/53] config: default to thumbnail on in point only --- app/config/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index ab2bbac5e..c9e814a16 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -105,7 +105,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); - SetEntryInternal(QStringLiteral("TimelineThumbnailMode"), NodeValue::kInt, Timeline::kThumbnailOn); + SetEntryInternal(QStringLiteral("TimelineThumbnailMode"), NodeValue::kInt, Timeline::kThumbnailInOut); SetEntryInternal(QStringLiteral("TimelineWaveformMode"), NodeValue::kInt, Timeline::kWaveformsEnabled); SetEntryInternal(QStringLiteral("DefaultVideoTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); From 7c0637d428a8c6e7ec55fcf63aeac681909cb718 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 8 Sep 2022 20:28:59 -0700 Subject: [PATCH 32/53] clip: remove unnecessary lines --- app/node/block/clip/clip.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index c898066ed..1b75d7eb0 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -423,10 +423,8 @@ void ClipBlock::InputValueChangedEvent(const QString &input, int element) if (Node *connected = GetConnectedOutput(kBufferIn)) { if (type == Track::kVideo) { emit connected->video_frame_cache()->CancelAll(); - //emit connected->thumbnail_cache()->CancelAll(); } else if (type == Track::kAudio) { emit connected->audio_playback_cache()->CancelAll(); - //emit connected->waveform_cache()->CancelAll(); } } } From 752616647c5aa781606cd6be08e6774fc0bb722f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 8 Sep 2022 20:29:36 -0700 Subject: [PATCH 33/53] rendermanager: separate waveform into another thread --- app/render/rendermanager.cpp | 51 +++++++++++++++++++----------------- app/render/rendermanager.h | 5 ++++ 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index cb3db2390..19a0d8efc 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -52,13 +52,10 @@ RenderManager::RenderManager(QObject *parent) : } if (context_) { - video_thread_ = new RenderThread(context_, decoder_cache_, shader_cache_, this); - dry_run_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this); - audio_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this); - - video_thread_->start(QThread::IdlePriority); - dry_run_thread_->start(QThread::IdlePriority); - audio_thread_->start(QThread::IdlePriority); + video_thread_ = CreateThread(context_); + dry_run_thread_ = CreateThread(); + audio_thread_ = CreateThread(); + waveform_thread_ = CreateThread(); } decoder_clear_timer_ = new QTimer(this); @@ -73,20 +70,24 @@ RenderManager::~RenderManager() delete shader_cache_; delete decoder_cache_; - video_thread_->quit(); - video_thread_->wait(); - - dry_run_thread_->quit(); - dry_run_thread_->wait(); + for (RenderThread *rt : render_threads_) { + rt->quit(); + rt->wait(); + } context_->PostDestroy(); delete context_; - - audio_thread_->quit(); - audio_thread_->wait(); } } +RenderThread *RenderManager::CreateThread(Renderer *renderer) +{ + auto t = new RenderThread(renderer, decoder_cache_, shader_cache_, this); + render_threads_.push_back(t); + t->start(QThread::IdlePriority); + return t; +} + RenderTicketPtr RenderManager::RenderFrame(const RenderVideoParams ¶ms) { // Create ticket @@ -133,22 +134,24 @@ RenderTicketPtr RenderManager::RenderAudio(const RenderAudioParams ¶ms) ticket->setProperty("aparam", QVariant::fromValue(params.audio_params)); ticket->setProperty("mode", params.mode); - audio_thread_->AddTicket(ticket); + if (params.generate_waveforms) { + waveform_thread_->AddTicket(ticket); + } else { + audio_thread_->AddTicket(ticket); + } return ticket; } bool RenderManager::RemoveTicket(RenderTicketPtr ticket) { - if (video_thread_->RemoveTicket(ticket)) { - return true; - } else if (audio_thread_->RemoveTicket(ticket)) { - return true; - } else if (dry_run_thread_->RemoveTicket(ticket)) { - return true; - } else { - return false; + for (RenderThread *rt : render_threads_) { + if (rt->RemoveTicket(ticket)) { + return true; + } } + + return false; } void RenderManager::SetAggressiveGarbageCollection(bool enabled) diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index e16cb5bd9..b33818b58 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -208,6 +208,8 @@ private: virtual ~RenderManager() override; + RenderThread *CreateThread(Renderer *renderer = nullptr); + static RenderManager* instance_; Renderer* context_; @@ -228,6 +230,9 @@ private: RenderThread *video_thread_; RenderThread *dry_run_thread_; RenderThread *audio_thread_; + RenderThread *waveform_thread_; + + std::list render_threads_; private slots: void ClearOldDecoders(); From 16b9cdda8dd51073aefc2033f075ff68410285b3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 8 Sep 2022 20:35:08 -0700 Subject: [PATCH 34/53] ffmpegdecoder: downsample frame where possible before gpu transfer --- app/codec/ffmpeg/ffmpegdecoder.cpp | 237 ++++++++++++++++------------- app/codec/ffmpeg/ffmpegdecoder.h | 4 + 2 files changed, 133 insertions(+), 108 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 3cb61d8f1..0d838ae03 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -164,125 +164,117 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) p.divider); TexturePtr tex = nullptr; - bool hwscale = true; // Attempt to use GLSL shader for faster YUV to RGB conversion - if (hwscale) { - if (src_fmt == AV_PIX_FMT_YUV420P - || src_fmt == AV_PIX_FMT_YUV422P - || src_fmt == AV_PIX_FMT_YUV444P - || src_fmt == AV_PIX_FMT_YUV420P10LE - || src_fmt == AV_PIX_FMT_YUV422P10LE - || src_fmt == AV_PIX_FMT_YUV444P10LE - || src_fmt == AV_PIX_FMT_YUV420P12LE - || src_fmt == AV_PIX_FMT_YUV422P12LE - || src_fmt == AV_PIX_FMT_YUV444P12LE) { - if (Yuv2RgbShader.isNull()) { - // Compile shader - Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); + if (IsPixelFormatGLSLCompatible(static_cast(src_fmt))) { + if (Yuv2RgbShader.isNull()) { + // Compile shader + Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); + } + + if (!Yuv2RgbShader.isNull()) { + int px_size; + int bits_per_pixel; + switch (src_fmt) { + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUV422P: + case AV_PIX_FMT_YUV444P: + default: + px_size = 1; + bits_per_pixel = 8; + break; + case AV_PIX_FMT_YUV420P10LE: + case AV_PIX_FMT_YUV422P10LE: + case AV_PIX_FMT_YUV444P10LE: + px_size = 2; + bits_per_pixel = 10; + break; + case AV_PIX_FMT_YUV420P12LE: + case AV_PIX_FMT_YUV422P12LE: + case AV_PIX_FMT_YUV444P12LE: + px_size = 2; + bits_per_pixel = 12; + break; } - if (!Yuv2RgbShader.isNull()) { - int px_size; - int bits_per_pixel; - switch (src_fmt) { - case AV_PIX_FMT_YUV420P: - case AV_PIX_FMT_YUV422P: - case AV_PIX_FMT_YUV444P: - default: - px_size = 1; - bits_per_pixel = 8; - break; - case AV_PIX_FMT_YUV420P10LE: - case AV_PIX_FMT_YUV422P10LE: - case AV_PIX_FMT_YUV444P10LE: - px_size = 2; - bits_per_pixel = 10; - break; - case AV_PIX_FMT_YUV420P12LE: - case AV_PIX_FMT_YUV422P12LE: - case AV_PIX_FMT_YUV444P12LE: - px_size = 2; - bits_per_pixel = 12; - break; - } + AVFrame *hw_in = f.get(); - VideoParams plane_params = vp; - plane_params.set_channel_count(1); + VideoParams plane_params = vp; + plane_params.set_channel_count(1); + plane_params.set_format(native_internal_pix_fmt_); + + if (p.divider != 1) { + ApplyScaler(f.get()); + hw_in = working_frame_; + } else { + // Fallback: shouldn't ever really get here, but just in case plane_params.set_divider(1); - plane_params.set_format(native_internal_pix_fmt_); - TexturePtr y_plane = p.renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size); - - if (src_fmt == AV_PIX_FMT_YUV420P - || src_fmt == AV_PIX_FMT_YUV422P - || src_fmt == AV_PIX_FMT_YUV420P10LE - || src_fmt == AV_PIX_FMT_YUV422P10LE - || src_fmt == AV_PIX_FMT_YUV420P12LE - || src_fmt == AV_PIX_FMT_YUV422P12LE) { - plane_params.set_width(plane_params.width()/2); - } - - if (src_fmt == AV_PIX_FMT_YUV420P - || src_fmt == AV_PIX_FMT_YUV420P10LE - || src_fmt == AV_PIX_FMT_YUV420P12LE) { - plane_params.set_height(plane_params.height()/2); - } - - TexturePtr u_plane = p.renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size); - TexturePtr v_plane = p.renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size); - - ShaderJob job; - job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane))); - job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); - job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); - job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); - job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, f->color_range == AVCOL_RANGE_JPEG)); - - const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f.get()->colorspace)); - job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kInt, yuv_coeffs[0])); - job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kInt, yuv_coeffs[2])); - job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kInt, yuv_coeffs[3])); - job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kInt, yuv_coeffs[1])); - - int interlacing = 0; - if (p.src_interlacing != VideoParams::kInterlaceNone) { - if (frame_rate_tb_.isNull()) { - frame_rate_tb_ = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), f.get()); - - // Double frame rate for interlaced fields - frame_rate_tb_ *= 2; - - // Flip frame rate so it can be used as a timebase - frame_rate_tb_.flip(); - } - - int64_t req = Timecode::time_to_timestamp(p.time, frame_rate_tb_); - int64_t frm = Timecode::rescale_timestamp(f->pts - instance_.avstream()->start_time, instance_.avstream()->time_base, frame_rate_tb_); - - bool first = (req == frm); - bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst); - - interlacing = (first == top_first) ? 1 : 2; - } - job.Insert(QStringLiteral("interlacing"), NodeValue(NodeValue::kInt, interlacing)); - job.Insert(QStringLiteral("pixel_height"), NodeValue(NodeValue::kInt, f->height)); - - tex = p.renderer->CreateTexture(vp); - p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); } + + TexturePtr y_plane = p.renderer->CreateTexture(plane_params, hw_in->data[0], hw_in->linesize[0] / px_size); + + if (src_fmt == AV_PIX_FMT_YUV420P + || src_fmt == AV_PIX_FMT_YUV422P + || src_fmt == AV_PIX_FMT_YUV420P10LE + || src_fmt == AV_PIX_FMT_YUV422P10LE + || src_fmt == AV_PIX_FMT_YUV420P12LE + || src_fmt == AV_PIX_FMT_YUV422P12LE) { + plane_params.set_width(plane_params.width()/2); + } + + if (src_fmt == AV_PIX_FMT_YUV420P + || src_fmt == AV_PIX_FMT_YUV420P10LE + || src_fmt == AV_PIX_FMT_YUV420P12LE) { + plane_params.set_height(plane_params.height()/2); + } + + TexturePtr u_plane = p.renderer->CreateTexture(plane_params, hw_in->data[1], hw_in->linesize[1] / px_size); + TexturePtr v_plane = p.renderer->CreateTexture(plane_params, hw_in->data[2], hw_in->linesize[2] / px_size); + + ShaderJob job; + job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane))); + job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); + job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); + job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); + job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, f->color_range == AVCOL_RANGE_JPEG)); + + const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f.get()->colorspace)); + job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kInt, yuv_coeffs[0])); + job.Insert(QStringLiteral("yuv_cgu"), NodeValue(NodeValue::kInt, yuv_coeffs[2])); + job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kInt, yuv_coeffs[3])); + job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kInt, yuv_coeffs[1])); + + int interlacing = 0; + if (p.src_interlacing != VideoParams::kInterlaceNone) { + if (frame_rate_tb_.isNull()) { + frame_rate_tb_ = av_guess_frame_rate(instance_.fmt_ctx(), instance_.avstream(), f.get()); + + // Double frame rate for interlaced fields + frame_rate_tb_ *= 2; + + // Flip frame rate so it can be used as a timebase + frame_rate_tb_.flip(); + } + + int64_t req = Timecode::time_to_timestamp(p.time, frame_rate_tb_); + int64_t frm = Timecode::rescale_timestamp(f->pts - instance_.avstream()->start_time, instance_.avstream()->time_base, frame_rate_tb_); + + bool first = (req == frm); + bool top_first = (p.src_interlacing == VideoParams::kInterlacedTopFirst); + + interlacing = (first == top_first) ? 1 : 2; + } + job.Insert(QStringLiteral("interlacing"), NodeValue(NodeValue::kInt, interlacing)); + job.Insert(QStringLiteral("pixel_height"), NodeValue(NodeValue::kInt, f->height)); + + tex = p.renderer->CreateTexture(vp); + p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); } } if (!tex) { // Fallback to software pixel format conversion - int r; - - r = av_buffersrc_add_frame_flags(buffersrc_ctx_, f.get(), AV_BUFFERSRC_FLAG_KEEP_REF); - if (r < 0) { - return nullptr; - } - r = av_buffersink_get_frame(buffersink_ctx_, working_frame_); - if (r < 0) { + if (!ApplyScaler(f.get())) { return nullptr; } @@ -717,6 +709,19 @@ const char *FFmpegDecoder::GetInterlacingModeInFFmpeg(VideoParams::Interlacing i } } +bool FFmpegDecoder::IsPixelFormatGLSLCompatible(AVPixelFormat f) +{ + return f == AV_PIX_FMT_YUV420P + || f == AV_PIX_FMT_YUV422P + || f == AV_PIX_FMT_YUV444P + || f == AV_PIX_FMT_YUV420P10LE + || f == AV_PIX_FMT_YUV422P10LE + || f == AV_PIX_FMT_YUV444P10LE + || f == AV_PIX_FMT_YUV420P12LE + || f == AV_PIX_FMT_YUV422P12LE + || f == AV_PIX_FMT_YUV444P12LE; +} + /* OLD UNUSED CODE: Keeping this around in case the code proves useful void FFmpegDecoder::CacheFrameToDisk(AVFrame *f) @@ -1023,7 +1028,7 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params } // Add format filter if necessary - if (ideal_pix_fmt != input->format) { + if (ideal_pix_fmt != input->format && !IsPixelFormatGLSLCompatible(static_cast(input->format))) { AVFilterContext* format_filter; snprintf(filter_args, kFilterArgSz, "pix_fmts=%u", ideal_pix_fmt); @@ -1097,6 +1102,22 @@ void FFmpegDecoder::RemoveFirstFrame() cache_at_zero_ = false; } +bool FFmpegDecoder::ApplyScaler(AVFrame *in) +{ + int r; + + r = av_buffersrc_add_frame_flags(buffersrc_ctx_, in, AV_BUFFERSRC_FLAG_KEEP_REF); + if (r < 0) { + return false; + } + r = av_buffersink_get_frame(buffersink_ctx_, working_frame_); + if (r < 0) { + return false; + } + + return true; +} + int FFmpegDecoder::MaximumQueueSize() { // Fairly arbitrary size. This used to need to be the number of current threads to ensure any diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 8df5a67bc..e018cfc75 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -142,6 +142,8 @@ private: static const char* GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing); + static bool IsPixelFormatGLSLCompatible(AVPixelFormat f); + AVFramePtr GetFrameFromCache(const int64_t &t) const; void ClearFrameCache(); @@ -150,6 +152,8 @@ private: void RemoveFirstFrame(); + bool ApplyScaler(AVFrame *in); + static int MaximumQueueSize(); RetrieveVideoParams filter_params_; From 53e510b87d93b2000b4827b7c2cc81817aa04df0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 8 Sep 2022 20:35:18 -0700 Subject: [PATCH 35/53] videoparams: change auto-divider target resolution --- app/render/videoparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index 14474d49c..34f93c74c 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -126,7 +126,7 @@ VideoParams::VideoParams(int width, int height, const rational &time_base, Forma int VideoParams::generate_auto_divider(qint64 width, qint64 height) { - const int target_res = 1920*1080; + const int target_res = 1280*720; qint64 megapixels = width * height; From d0e12ce60767a6820979df90d34430407ea11867 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 13 Sep 2022 19:19:42 -0700 Subject: [PATCH 36/53] timeline: add options for manually caching and discarding parts of clips --- app/node/block/clip/clip.cpp | 31 ++++++++- app/node/block/clip/clip.h | 5 +- app/widget/timelinewidget/timelinewidget.cpp | 70 ++++++++++++++++++-- app/widget/timelinewidget/timelinewidget.h | 4 ++ app/widget/timelinewidget/tool/import.cpp | 3 +- 5 files changed, 101 insertions(+), 12 deletions(-) diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 1b75d7eb0..787cf5442 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -136,6 +136,18 @@ void ClipBlock::SetAutocache(bool e) SetStandardValue(kAutoCacheInput, e); } +void ClipBlock::DiscardCache() +{ + if (Node *connected = GetConnectedOutput(kBufferIn)) { + Track::Type type = GetTrackType(); + if (type == Track::kVideo) { + connected->video_frame_cache()->Invalidate(TimeRange(RATIONAL_MIN, RATIONAL_MAX)); + } else if (type == Track::kAudio) { + connected->audio_playback_cache()->Invalidate(TimeRange(RATIONAL_MIN, RATIONAL_MAX)); + } + } +} + rational ClipBlock::SequenceToMediaTime(const rational &sequence_time, uint64_t flags) const { // These constants are not considered "values" per se, so we don't modify them @@ -237,13 +249,18 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range) } } -void ClipBlock::RequestInvalidatedFromConnected() +void ClipBlock::RequestInvalidatedFromConnected(bool force_all, const TimeRange &intersect) { Track::Type type = GetTrackType(); if (type == Track::kVideo || type == Track::kAudio) { if (Node *connected = GetConnectedOutput(kBufferIn)) { TimeRange max_range = media_range(); + + if (!intersect.length().isNull()) { + max_range = max_range.Intersected(intersect); + } + if (type == Track::kVideo) { // Handle thumbnails TimeRange thumb_range = max_range; @@ -252,7 +269,7 @@ void ClipBlock::RequestInvalidatedFromConnected() } // Handle video cache - if (IsAutocaching()) { + if (IsAutocaching() || force_all) { RequestInvalidatedForCache(connected->video_frame_cache(), max_range); } } else if (type == Track::kAudio) { @@ -262,7 +279,7 @@ void ClipBlock::RequestInvalidatedFromConnected() } // Handle audio cache - if (IsAutocaching()) { + if (IsAutocaching() || force_all) { RequestInvalidatedForCache(connected->audio_playback_cache(), max_range); } } @@ -391,6 +408,10 @@ void ClipBlock::InputConnectedEvent(const QString &input, int element, Node *out super::InputConnectedEvent(input, element, output); if (input == kBufferIn) { + connect(output->thumbnail_cache(), &FrameHashCache::Invalidated, this, &Block::PreviewChanged); + connect(output->waveform_cache(), &AudioPlaybackCache::Invalidated, this, &Block::PreviewChanged); + connect(output->video_frame_cache(), &FrameHashCache::Invalidated, this, &Block::PreviewChanged); + connect(output->audio_playback_cache(), &AudioPlaybackCache::Invalidated, this, &Block::PreviewChanged); connect(output->thumbnail_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); connect(output->waveform_cache(), &AudioPlaybackCache::Validated, this, &Block::PreviewChanged); connect(output->video_frame_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); @@ -403,6 +424,10 @@ void ClipBlock::InputDisconnectedEvent(const QString &input, int element, Node * super::InputDisconnectedEvent(input, element, output); if (input == kBufferIn) { + disconnect(output->thumbnail_cache(), &FrameHashCache::Invalidated, this, &Block::PreviewChanged); + disconnect(output->waveform_cache(), &AudioPlaybackCache::Invalidated, this, &Block::PreviewChanged); + disconnect(output->video_frame_cache(), &FrameHashCache::Invalidated, this, &Block::PreviewChanged); + disconnect(output->audio_playback_cache(), &AudioPlaybackCache::Invalidated, this, &Block::PreviewChanged); disconnect(output->thumbnail_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); disconnect(output->waveform_cache(), &AudioPlaybackCache::Validated, this, &Block::PreviewChanged); disconnect(output->video_frame_cache(), &FrameHashCache::Validated, this, &Block::PreviewChanged); diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 8f98ee80f..5c84a1eca 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -63,6 +63,8 @@ public: bool IsAutocaching() const { return GetStandardValue(kAutoCacheInput).toBool(); } void SetAutocache(bool e); + void DiscardCache(); + virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override; virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override; @@ -73,7 +75,7 @@ public: virtual void Retranslate() override; - void RerequestCaches() { RequestInvalidatedFromConnected(); } + void RequestInvalidatedFromConnected(bool force_all = false, const TimeRange &intersect = TimeRange()); double speed() const { @@ -227,7 +229,6 @@ private: rational MediaToSequenceTime(const rational& media_time) const; void RequestRangeFromConnected(const TimeRange &range); - void RequestInvalidatedFromConnected(); void RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range, bool invalidate, bool request); void RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 44e63c687..f54792aef 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1110,10 +1110,26 @@ void TimelineWidget::ShowContextMenu() menu.addSeparator(); if (ClipBlock *clip = dynamic_cast(selected.first())) { - QAction *autocache_action = menu.addAction(tr("Auto-Cache")); - autocache_action->setCheckable(true); - autocache_action->setChecked(clip->IsAutocaching()); - connect(autocache_action, &QAction::triggered, this, &TimelineWidget::SetSelectedClipsAutocaching); + { + Menu *cache_menu = new Menu(tr("Cache"), &menu); + menu.addMenu(cache_menu); + + QAction *autocache_action = cache_menu->addAction(tr("Auto-Cache")); + autocache_action->setCheckable(true); + autocache_action->setChecked(clip->IsAutocaching()); + connect(autocache_action, &QAction::triggered, this, &TimelineWidget::SetSelectedClipsAutocaching); + + cache_menu->addSeparator(); + + auto cache_clip = cache_menu->addAction(tr("Cache All")); + connect(cache_clip, &QAction::triggered, this, &TimelineWidget::CacheClips); + + auto cache_inout = cache_menu->addAction(tr("Cache In/Out")); + connect(cache_inout, &QAction::triggered, this, &TimelineWidget::CacheClipsInOut); + + auto cache_discard = cache_menu->addAction(tr("Discard")); + connect(cache_discard, &QAction::triggered, this, &TimelineWidget::CacheDiscard); + } if (clip->connected_viewer()) { QAction *reveal_in_footage_viewer = menu.addAction(tr("Reveal in Footage Viewer")); @@ -1145,7 +1161,7 @@ void TimelineWidget::ShowContextMenu() menu.addMenu(thumbnail_menu); thumbnail_menu->AddActionWithData(tr("Disabled"), Timeline::kThumbnailOff, OLIVE_CONFIG("TimelineThumbnailMode")); - thumbnail_menu->AddActionWithData(tr("Only At In/Out Points"), Timeline::kThumbnailInOut, OLIVE_CONFIG("TimelineThumbnailMode")); + thumbnail_menu->AddActionWithData(tr("Only At In Points"), Timeline::kThumbnailInOut, OLIVE_CONFIG("TimelineThumbnailMode")); thumbnail_menu->AddActionWithData(tr("Enabled"), Timeline::kThumbnailOn, OLIVE_CONFIG("TimelineThumbnailMode")); connect(thumbnail_menu, &Menu::triggered, this, &TimelineWidget::SetViewThumbnailsEnabled); @@ -1311,6 +1327,50 @@ void TimelineWidget::SetSelectedClipsAutocaching(bool e) Core::instance()->undo_stack()->pushIfHasChildren(command); } +void TimelineWidget::CacheClips() +{ + for (Block *b : selected_blocks_) { + if (ClipBlock *clip = dynamic_cast(b)) { + clip->RequestInvalidatedFromConnected(true); + } + } +} + +void TimelineWidget::CacheClipsInOut() +{ + if (!this->sequence() || !this->sequence()->GetWorkArea()->enabled()) { + return; + } + + TimeTargetObject tto; + tto.SetTimeTarget(this->sequence()); + + const TimeRange &r = this->sequence()->GetWorkArea()->range(); + for (Block *b : qAsConst(selected_blocks_)) { + if (ClipBlock *clip = dynamic_cast(b)) { + if (Node *connected = clip->GetConnectedOutput(clip->kBufferIn)) { + TimeRange adjusted = tto.GetAdjustedTime(this->sequence(), connected, r, true); + clip->RequestInvalidatedFromConnected(true, adjusted); + } + } + } +} + +void TimelineWidget::CacheDiscard() +{ + if (QMessageBox::question(this, tr("Discard Cache"), + tr("This will discard all cache for this clip. " + "If the clip has auto-cache enabled, it will be recached immediately. " + "This cannot be undone.\n\n" + "Do you wish to continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (Block *b : selected_blocks_) { + if (ClipBlock *clip = dynamic_cast(b)) { + clip->DiscardCache(); + } + } + } +} + void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost) { ghost_items_.append(ghost); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index b2f869443..d952c5419 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -440,6 +440,10 @@ private slots: void SetSelectedClipsAutocaching(bool e); + void CacheClips(); + void CacheClipsInOut(); + void CacheDiscard(); + }; } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 73a100bff..02dec714d 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -503,7 +503,7 @@ void ImportTool::DropGhosts(bool insert) Core::instance()->undo_stack()->pushIfHasChildren(command); while (!imported_clips.empty()) { - imported_clips.front()->RerequestCaches(); + imported_clips.front()->RequestInvalidatedFromConnected(); imported_clips.pop_front(); } @@ -523,7 +523,6 @@ TimelineViewGhostItem* ImportTool::CreateGhost(const TimeRange &range, const rat snap_points_.push_back(ghost->GetIn()); snap_points_.push_back(ghost->GetOut()); - ghost->SetMode(Timeline::kMove); parent()->AddGhost(ghost); From d78e87aa3a182409b86a0dc33632a5d7db98783e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 13 Sep 2022 20:16:07 -0700 Subject: [PATCH 37/53] viewer: fix scrubbing regression --- app/widget/viewer/viewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index cf12ad0cd..46bf1ab0b 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -940,7 +940,7 @@ void ViewerWidget::PushScrubbedAudio() if (ignore_scrub_ == 0) { // Get audio src device from renderer - const AudioParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters(); + const AudioParams& params = GetConnectedNode()->GetAudioParams(); if (params.is_valid()) { // NOTE: Hardcoded scrubbing interval (20ms) From ea8a2fad9d264fd3db52a4eb0478e109d5a64a06 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 13 Sep 2022 20:26:40 -0700 Subject: [PATCH 38/53] menus: hide sequence cache options --- app/widget/viewer/viewer.cpp | 3 ++- app/window/mainwindow/mainmenu.cpp | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 46bf1ab0b..d786d1b48 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -1293,6 +1293,7 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) menu.addSeparator(); + /* TEMP: Hide sequence cache options. Want to see if clip caching supersedes it. { Menu* cache_menu = new Menu(tr("Cache"), &menu); menu.addMenu(cache_menu); @@ -1304,7 +1305,7 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) // Cache In/Out Sequence QAction* cache_inout_sequence = cache_menu->addAction(tr("Cache Sequence In/Out")); connect(cache_inout_sequence, &QAction::triggered, this, &ViewerWidget::CacheSequenceInOut); - } + }*/ menu.addSeparator(); diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index fd8123371..22ae0e70d 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -183,6 +183,10 @@ MainMenu::MainMenu(MainWindow *parent) : sequence_disk_cache_clear_item_ = sequence_menu_->AddItem("seqcacheclear", this, &MainMenu::SequenceCacheClearTriggered); + // TEMP: Hide sequence cache items for now. Want to see if clip caching will supersede it. + sequence_cache_item_->setVisible(false); + sequence_cache_in_to_out_item_->setVisible(false); + // // WINDOW MENU // From bd3f024d0150a9dd69dac14a7d3a2578ca5b51dc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 14 Sep 2022 10:08:43 -0700 Subject: [PATCH 39/53] render: check for more cancels --- app/render/renderprocessor.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 1e69ea2ce..92d61c041 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -144,6 +144,11 @@ void RenderProcessor::Run() SetCacheVideoParams(ticket_->property("vparam").value()); SetCacheAudioParams(ticket_->property("aparam").value()); + if (IsCancelled()) { + ticket_->Finish(); + return; + } + switch (type) { case RenderManager::kTypeVideo: { @@ -225,11 +230,11 @@ void RenderProcessor::Run() SampleBuffer samples = sample_val.toSamples(); if (samples.is_allocated()) { - if (ticket_->property("clamp").toBool()) { + if (ticket_->property("clamp").toBool() && !IsCancelled()) { samples.clamp(); } - if (ticket_->property("enablewaveforms").toBool()) { + if (ticket_->property("enablewaveforms").toBool() && !IsCancelled()) { AudioVisualWaveform vis; vis.set_channel_count(samples.audio_params().channel_count()); vis.OverwriteSamples(samples, samples.audio_params().sample_rate()); From e91da63d16aaea9efc86884b8c34a3c749b5321c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 14 Sep 2022 10:09:26 -0700 Subject: [PATCH 40/53] render: limit running audio tasks --- 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 beb94aeb2..065b7c6a1 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -650,7 +650,7 @@ void PreviewAutoCacher::TryRender() } // Handle audio tasks - while (!pending_audio_jobs_.empty()) { + while (!pending_audio_jobs_.empty() && running_audio_tasks_.size() < max_tasks) { AudioJob &d = pending_audio_jobs_.front(); // Start job From 4361dbd17b510f3a690f0e0c35ff524d63b6bf94 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 14 Sep 2022 13:40:12 -0700 Subject: [PATCH 41/53] render: return to caching partial waveforms --- app/render/audiowaveformcache.cpp | 24 +++++++++++++++++++++++- app/render/audiowaveformcache.h | 12 +++++++++++- app/render/previewautocacher.cpp | 25 +++++++++++++++++++++---- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/app/render/audiowaveformcache.cpp b/app/render/audiowaveformcache.cpp index b902b1957..5ed1ee280 100644 --- a/app/render/audiowaveformcache.cpp +++ b/app/render/audiowaveformcache.cpp @@ -31,15 +31,25 @@ void AudioWaveformCache::WriteWaveform(const TimeRange &range, const TimeRangeLi { // 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(); - wv.waveform = waveform->Mid(local_start, r.length()); + if (local_start != 0) { + wv.waveform = waveform->Mid(local_start, r.length()); + } else { + wv.waveform = *waveform; + } waveforms_.append(wv); } +#else + if (waveform) { + waveforms_.OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length()); + } +#endif Validate(r); } @@ -50,6 +60,7 @@ void AudioWaveformCache::Draw(QPainter *painter, const QRect &rect, const double rational end = start_time + rational::fromDouble(rect.width() / scale); TimeRange draw_range(start_time, end); +#ifdef AVW_USE_LIST foreach (const TimeRangeWithWaveform &wv, waveforms_) { if (wv.OverlapsWith(draw_range)) { rational substart = std::max(wv.in(), draw_range.in()); @@ -63,10 +74,14 @@ void AudioWaveformCache::Draw(QPainter *painter, const QRect &rect, const double AudioVisualWaveform::DrawWaveform(painter, subrect, scale, wv.waveform, local_start); } } +#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); @@ -85,10 +100,14 @@ AudioVisualWaveform::Sample AudioWaveformCache::GetSummaryFromTime(const rationa } return result; +#else + return waveforms_.GetSummaryFromTime(start, length); +#endif } rational AudioWaveformCache::length() const { +#ifdef AVW_USE_LIST rational len = 0; foreach (const TimeRangeWithWaveform &wv, waveforms_) { @@ -96,6 +115,9 @@ rational AudioWaveformCache::length() const } return len; +#else + return waveforms_.length(); +#endif } void AudioWaveformCache::SetPassthrough(PlaybackCache *cache) diff --git a/app/render/audiowaveformcache.h b/app/render/audiowaveformcache.h index 6dcf58f07..a1a6dcfe5 100644 --- a/app/render/audiowaveformcache.h +++ b/app/render/audiowaveformcache.h @@ -24,6 +24,8 @@ #include "audio/audiovisualwaveform.h" #include "playbackcache.h" +//#define AVW_USE_LIST + namespace olive { class AudioWaveformCache : public PlaybackCache @@ -35,7 +37,11 @@ public: void WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform); const AudioParams &GetParameters() const { return params_; } - void SetParameters(const AudioParams &p) { params_ = p; } + void SetParameters(const AudioParams &p) + { + params_ = p; + waveforms_.set_channel_count(p.channel_count()); + } void Draw(QPainter* painter, const QRect &rect, const double &scale, const rational &start_time) const; @@ -46,6 +52,7 @@ public: virtual void SetPassthrough(PlaybackCache *cache) override; private: +#ifdef AVW_USE_LIST class TimeRangeWithWaveform : public TimeRange { public: @@ -77,6 +84,9 @@ private: }; QVector waveforms_; +#else + AudioVisualWaveform waveforms_; +#endif AudioParams params_; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 065b7c6a1..5a9a5a21e 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -156,17 +156,17 @@ void PreviewAutoCacher::AudioRendered() AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value(); SampleBuffer buf = watcher->Get().value(); - node->audio_playback_cache()->SetParameters(buf.audio_params()); - node->waveform_cache()->SetParameters(buf.audio_params()); bool incomplete = watcher->GetTicket()->property("incomplete").toBool(); if (AudioPlaybackCache *pcm = dynamic_cast(cache)) { // WritePCM is tolerant to its buffer being null, it will just write silence instead + pcm->SetParameters(buf.audio_params()); pcm->WritePCM(range, valid_ranges, watcher->Get().value()); } else if (AudioWaveformCache *wave = dynamic_cast(cache)) { + wave->SetParameters(buf.audio_params()); if (!incomplete) { wave->WriteWaveform(range, valid_ranges, &waveform); } @@ -653,14 +653,31 @@ void PreviewAutoCacher::TryRender() while (!pending_audio_jobs_.empty() && running_audio_tasks_.size() < max_tasks) { AudioJob &d = pending_audio_jobs_.front(); + bool pop = true; + // Start job if (Node *copy = copy_map_.value(d.node)) { - RenderAudio(copy, d.range, d.cache); + TimeRange &queued_range = d.range; + TimeRange use_range = queued_range; + + if (dynamic_cast(d.cache)) { + rational new_out = std::min(use_range.in() + AudioVisualWaveform::kMinimumSampleRate.flipped(), use_range.out()); + + if (new_out != use_range.out()) { + use_range.set_out(new_out); + queued_range.set_in(new_out); + pop = false; + } + } + + RenderAudio(copy, use_range, d.cache); } else { qCritical() << "Failed to find node copy for audio job"; } - pending_audio_jobs_.pop_front(); + if (pop) { + pending_audio_jobs_.pop_front(); + } } } } From cce44e40f4bf0de21bb4b576009ff15f862f8f9b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 14 Sep 2022 13:40:35 -0700 Subject: [PATCH 42/53] project: implement searching I can't believe this has been unimplemented this whole time... --- app/panel/project/project.cpp | 1 + app/widget/projectexplorer/projectexplorer.cpp | 6 ++++++ app/widget/projectexplorer/projectexplorer.h | 2 ++ 3 files changed, 9 insertions(+) diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index d97eb7bd3..dcbb72fe3 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -58,6 +58,7 @@ ProjectPanel::ProjectPanel(QWidget *parent) : layout->addWidget(explorer_); connect(explorer_, &ProjectExplorer::DoubleClickedItem, this, &ProjectPanel::ItemDoubleClickSlot); connect(explorer_, &ProjectExplorer::SelectionChanged, this, &ProjectPanel::SelectionChanged); + connect(toolbar, &ProjectToolbar::SearchChanged, explorer_, &ProjectExplorer::SetSearchFilter); // Set toolbar's view to the explorer's view toolbar->SetView(explorer_->view_type()); diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index bdf853d99..48cc8e3aa 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -67,6 +67,7 @@ ProjectExplorer::ProjectExplorer(QWidget *parent) : // Set up sort filter proxy model sort_model_.setSourceModel(&model_); + sort_model_.setFilterCaseSensitivity(Qt::CaseInsensitive); sort_model_.setSortRole(ProjectViewModel::kInnerTextRole); // Add tree view to stacked widget @@ -301,6 +302,11 @@ void ProjectExplorer::RenameSelectedItem() } } +void ProjectExplorer::SetSearchFilter(const QString &s) +{ + sort_model_.setFilterFixedString(s); +} + void ProjectExplorer::ShowContextMenu() { Menu menu; diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index d69f1b8e3..c03fc2e67 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -94,6 +94,8 @@ public slots: void RenameSelectedItem(); + void SetSearchFilter(const QString &s); + signals: /** * @brief Emitted when an Item is double clicked From 5dde385d975e9d8eb3cad40931527ef7225979c7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 15 Sep 2022 16:10:04 -0700 Subject: [PATCH 43/53] sequencedialog: hide auto-cache option --- app/dialog/sequence/sequencedialogparametertab.cpp | 5 ++++- app/dialog/sequence/sequencedialogparametertab.h | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp index 1d0fa868e..a277717ac 100644 --- a/app/dialog/sequence/sequencedialogparametertab.cpp +++ b/app/dialog/sequence/sequencedialogparametertab.cpp @@ -73,10 +73,13 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg preview_layout->addWidget(new QLabel(tr("Quality:")), row, 0); preview_format_field_ = new PixelFormatComboBox(false); preview_layout->addWidget(preview_format_field_, row, 1, 1, 2); + + /* TEMP: Disable sequence auto-cache, wanna see if clip cache supersedes it. row++; preview_layout->addWidget(new QLabel(tr("Auto-Cache:")), row, 0); + preview_layout->addWidget(preview_autocache_field_, row, 1);*/ preview_autocache_field_ = new QCheckBox(); - preview_layout->addWidget(preview_autocache_field_, row, 1); + layout->addWidget(preview_group); // Set values based on input sequence diff --git a/app/dialog/sequence/sequencedialogparametertab.h b/app/dialog/sequence/sequencedialogparametertab.h index e11561b2f..97a5f24a7 100644 --- a/app/dialog/sequence/sequencedialogparametertab.h +++ b/app/dialog/sequence/sequencedialogparametertab.h @@ -66,7 +66,9 @@ public: bool GetSelectedPreviewAutoCache() const { - return preview_autocache_field_->isChecked(); + //return preview_autocache_field_->isChecked(); + // TEMP: Disable sequence auto-cache, wanna see if clip cache supersedes it. + return false; } public slots: From 22b618da9640b065ef0f4c8fb98ecb36b6fdf527 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 16 Sep 2022 00:00:39 -0700 Subject: [PATCH 44/53] mathbase: use size_t for sample counts instead of int --- app/node/math/math/mathbase.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index a248bb41c..14045b054 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -301,21 +301,21 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt SampleBuffer samples_a = val_a.toSamples(); SampleBuffer samples_b = val_b.toSamples(); - int max_samples = qMax(samples_a.sample_count(), samples_b.sample_count()); - int min_samples = qMin(samples_a.sample_count(), samples_b.sample_count()); + size_t max_samples = qMax(samples_a.sample_count(), samples_b.sample_count()); + size_t min_samples = qMin(samples_a.sample_count(), samples_b.sample_count()); SampleBuffer mixed_samples = SampleBuffer(samples_a.audio_params(), max_samples); for (int i=0;i(operation, samples_a.data(i)[j], samples_b.data(i)[j]); } } if (max_samples > min_samples) { // Fill in remainder space with 0s - int remainder = max_samples - min_samples; + size_t remainder = max_samples - min_samples; const SampleBuffer &larger_buffer = (max_samples == samples_a.sample_count()) ? samples_a : samples_b; From 3003b4e849253b053e88130d4d9ade420fb0f0cb Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 16 Sep 2022 00:09:38 -0700 Subject: [PATCH 45/53] crossdissolvetransition: use size_t instead of int for sample counts --- .../block/transition/crossdissolve/crossdissolvetransition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp index 9a0622f75..89ef31790 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp @@ -55,7 +55,7 @@ ShaderCode CrossDissolveTransition::GetShaderCode(const ShaderRequest &request) void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples, const SampleBuffer &to_samples, SampleBuffer &out_samples, double time_in) const { - for (int i=0; i Date: Fri, 16 Sep 2022 00:35:48 -0700 Subject: [PATCH 46/53] fix more instances where sample counts used int instead of size_t --- .../transition/crossdissolve/crossdissolvetransition.cpp | 6 +++--- app/render/renderprocessor.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp index 89ef31790..28264a446 100644 --- a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp +++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp @@ -70,9 +70,9 @@ void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples, c if (to_samples.is_allocated()) { // Offset input samples from the end - int in_index = i - (out_samples.sample_count() - to_samples.sample_count()); - - if (in_index >= 0) { + size_t remain = (out_samples.sample_count() - to_samples.sample_count()); + if (i >= remain) { + qint64 in_index = i - remain; out_samples.data(j)[i] += to_samples.data(j)[in_index] * TransformCurve(progress); } } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 92d61c041..426b0a9ed 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -558,7 +558,7 @@ void RenderProcessor::ProcessSamples(SampleBuffer &destination, const Node *node const AudioParams& audio_params = GetCacheAudioParams(); - for (int i=0;i(i) / static_cast(audio_params.sample_rate()); From f38107109cd27c3a7d8a431587ee9c8572caf569 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 16 Sep 2022 00:56:03 -0700 Subject: [PATCH 47/53] audiomonitor: use size_t for std vector --- app/widget/audiomonitor/audiomonitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/audiomonitor/audiomonitor.cpp b/app/widget/audiomonitor/audiomonitor.cpp index 2f68a272f..f298fda10 100644 --- a/app/widget/audiomonitor/audiomonitor.cpp +++ b/app/widget/audiomonitor/audiomonitor.cpp @@ -316,7 +316,7 @@ void AudioMonitor::UpdateValuesFromWaveform(QVector &v, qint64 delta_tim void AudioMonitor::AudioVisualWaveformSampleToInternalValues(const AudioVisualWaveform::Sample &in, QVector &out) { - for (int i=0; i Date: Fri, 16 Sep 2022 01:12:56 -0700 Subject: [PATCH 48/53] tests: update test for new code --- tests/general/timerange-tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/general/timerange-tests.cpp b/tests/general/timerange-tests.cpp index 6ddfe14e1..e20b10112 100644 --- a/tests/general/timerange-tests.cpp +++ b/tests/general/timerange-tests.cpp @@ -86,13 +86,13 @@ OLIVE_ADD_TEST(TimeRangeListFrameIteratorSize) ranges.insert(TimeRange(rational(1402, 20), rational(1403, 20))); // 1 ranges.insert(TimeRange(rational(10001, 40), rational(10002, 40))); // 0 ranges.insert(TimeRange(rational(10001, 40), rational(10004, 40))); // 0 - ranges.insert(TimeRange(rational(10001, 40), rational(10005, 40))); // 1 + ranges.insert(TimeRange(rational(10001, 40), rational(10005, 40))); // 2 TimeRangeListFrameIterator iterator(ranges, timebase); QVector vec = iterator.ToVector(); - OLIVE_ASSERT_EQUAL(vec.size(), 253); + OLIVE_ASSERT_EQUAL(vec.size(), 254); OLIVE_ASSERT_EQUAL(iterator.size(), vec.size()); TimeRangeListFrameIterator empty(TimeRangeList(), timebase); From 02ee079bde4f502e550b018dc0cc67d12e0f9a3a Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 16 Sep 2022 11:28:03 -0700 Subject: [PATCH 49/53] timelinemarker: fixed issue with undoing resized markers --- app/timeline/timelinemarker.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 25de1ab9a..a9753b2e5 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -331,7 +331,6 @@ Project* MarkerChangeTimeCommand::GetRelevantProject() const void MarkerChangeTimeCommand::redo() { - old_time_ = marker_->time(); marker_->set_time(new_time_); } From 044551cca93b6535f50529fbd55ff632e66099af Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 16 Sep 2022 11:54:35 -0700 Subject: [PATCH 50/53] colormanager: reset default cs on config change --- app/node/color/colormanager/colormanager.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/node/color/colormanager/colormanager.cpp b/app/node/color/colormanager/colormanager.cpp index a8a446208..4dfac3e6c 100644 --- a/app/node/color/colormanager/colormanager.cpp +++ b/app/node/color/colormanager/colormanager.cpp @@ -272,7 +272,21 @@ void ColorManager::InputValueChangedEvent(const QString &input, int element) if (input == kConfigFilenameIn) { try { + QString old_default_cs = GetDefaultInputColorSpace(); + SetConfig(OCIO::Config::CreateFromFile(GetConfigFilename().toUtf8())); + + // Set new default colorspace appropriately + int new_default = 0; + QStringList available_cs = ListAvailableColorspaces(); + for (int i=0; i Date: Fri, 16 Sep 2022 12:19:47 -0700 Subject: [PATCH 51/53] viewer: add shortcut to save frame as image --- app/core.cpp | 13 +++++++++---- app/core.h | 2 ++ app/dialog/export/export.cpp | 17 ++++++++++++----- app/dialog/export/export.h | 7 ++++++- app/widget/viewer/viewer.cpp | 10 ++++++++++ app/widget/viewer/viewer.h | 2 ++ 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index d6989319a..0aa5fa11a 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -394,10 +394,7 @@ void Core::DialogExportShow() rational time; if (GetSequenceToExport(&viewer, &time)) { - ExportDialog* ed = new ExportDialog(viewer, main_window_); - ed->SetTime(time); - connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater); - ed->open(); + OpenExportDialogForViewer(viewer, time, false); } } @@ -1253,6 +1250,14 @@ void Core::OpenNodeInViewer(ViewerOutput *viewer) main_window_->OpenNodeInViewer(viewer); } +void Core::OpenExportDialogForViewer(ViewerOutput *viewer, const rational &time, bool start_still_image) +{ + ExportDialog* ed = new ExportDialog(viewer, start_still_image, main_window_); + ed->SetTime(time); + connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater); + ed->open(); +} + void Core::CheckForAutoRecoveries() { QFile autorecovery_index(GetAutoRecoveryIndexFilename()); diff --git a/app/core.h b/app/core.h index 1c7a923ef..c1902f17a 100644 --- a/app/core.h +++ b/app/core.h @@ -313,6 +313,8 @@ public: void OpenNodeInViewer(ViewerOutput* viewer); + void OpenExportDialogForViewer(ViewerOutput *viewer, const rational &time, bool start_still_image); + public slots: /** * @brief Starts an open file dialog to load a project from file diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 54a1403a8..0ffcbb4c3 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -44,9 +44,10 @@ namespace olive { #define super QDialog -ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : +ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWidget *parent) : super(parent), - viewer_node_(viewer_node) + viewer_node_(viewer_node), + stills_only_mode_(stills_only_mode) { QHBoxLayout* layout = new QHBoxLayout(this); @@ -238,7 +239,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : subtitles_enabled_->setEnabled(has_subtitle_tracks); // If the viewer already has cached params, use them - if (viewer_node_->GetLastUsedEncodingParams().IsValid()) { + if (!stills_only_mode_ && viewer_node_->GetLastUsedEncodingParams().IsValid()) { SetParams(viewer_node_->GetLastUsedEncodingParams()); } else { SetDefaults(); @@ -572,7 +573,11 @@ bool ExportDialog::SequenceHasSubtitles() const void ExportDialog::SetDefaults() { - format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video); + if (!stills_only_mode_) { + format_combobox_->SetFormat(ExportFormat::kFormatMPEG4Video); + } else { + format_combobox_->SetFormat(ExportFormat::kFormatPNG); + } FormatChanged(format_combobox_->GetFormat()); VideoParams vp = viewer_node_->GetVideoParams(); @@ -745,7 +750,9 @@ void ExportDialog::done(int r) { preview_viewer_->ConnectViewerNode(nullptr); - viewer_node_->SetLastUsedEncodingParams(GenerateParams()); + if (!stills_only_mode_) { + viewer_node_->SetLastUsedEncodingParams(GenerateParams()); + } super::done(r); } diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index b9cd62b73..2b257cec2 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -43,7 +43,10 @@ class ExportDialog : public QDialog { Q_OBJECT public: - ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr); + ExportDialog(ViewerOutput* viewer_node, bool stills_only_mode, QWidget* parent = nullptr); + ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr) : + ExportDialog(viewer_node, false, parent) + {} rational GetSelectedTimebase() const; void SetSelectedTimebase(const rational &r); @@ -116,6 +119,8 @@ private: QWidget* preferences_area_; QCheckBox *export_bkg_box_; + bool stills_only_mode_; + private slots: void BrowseFilename(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index d786d1b48..134874425 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -588,6 +588,11 @@ void ViewerWidget::RequestNextDryRun() } } +void ViewerWidget::SaveFrameAsImage() +{ + Core::instance()->OpenExportDialogForViewer(GetConnectedNode(), GetTime(), true); +} + void ViewerWidget::CloseAudioProcessor() { audio_processor_.Close(); @@ -1385,6 +1390,11 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) }); } + menu.addSeparator(); + + auto save_frame_as_image = menu.addAction(tr("Save Frame As Image")); + connect(save_frame_as_image, &QAction::triggered, this, &ViewerWidget::SaveFrameAsImage); + menu.exec(static_cast(sender())->mapToGlobal(pos)); } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index e4aaa6f0e..4c3a18551 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -368,6 +368,8 @@ private slots: void RequestNextDryRun(); + void SaveFrameAsImage(); + }; } From 05551ed59d5c5067c892f2684f9eb69d5076ffd6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 16 Sep 2022 12:20:51 -0700 Subject: [PATCH 52/53] playbackcache: don't save state if diskmanager is not present --- app/render/playbackcache.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 2de3a6447..0f179dab8 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -121,6 +121,10 @@ void PlaybackCache::LoadState() void PlaybackCache::SaveState() { + if (!DiskManager::instance()) { + return; + } + QDir cache_dir = GetThisCacheDirectory(); QFile f(cache_dir.filePath(QStringLiteral("state"))); if (validated_.isEmpty() && passthroughs_.isEmpty()) { From 6e0c6cdc52da2b7204f6b33f0a080feb0828a175 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 16 Sep 2022 12:35:40 -0700 Subject: [PATCH 53/53] exportdialog: add option to reimport result --- app/core.cpp | 8 ++++++++ app/core.h | 2 ++ app/dialog/export/export.cpp | 35 +++++++++++++++++++++++++++++------ app/dialog/export/export.h | 4 ++++ 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 0aa5fa11a..97a49f8e0 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1256,6 +1256,7 @@ void Core::OpenExportDialogForViewer(ViewerOutput *viewer, const rational &time, ed->SetTime(time); connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater); ed->open(); + connect(ed, &ExportDialog::RequestImportFile, this, &Core::ImportSingleFile); } void Core::CheckForAutoRecoveries() @@ -1421,6 +1422,13 @@ void Core::OpenProjectInternal(const QString &filename, bool recovery_project) task_dialog->open(); } +void Core::ImportSingleFile(const QString &f) +{ + if (Project *p = GetActiveProject()) { + ImportFiles({f}, p->root()); + } +} + int Core::CountFilesInFileList(const QFileInfoList &filenames) { int file_count = 0; diff --git a/app/core.h b/app/core.h index c1902f17a..a2ded04db 100644 --- a/app/core.h +++ b/app/core.h @@ -656,6 +656,8 @@ private slots: */ void OpenProjectInternal(const QString& filename, bool recovery_project = false); + void ImportSingleFile(const QString &f); + }; } diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 0ffcbb4c3..6210b63f4 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -157,6 +157,30 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi row++; + { + QGroupBox *options_group = new QGroupBox(); + preferences_layout->addWidget(options_group, row, 0, 1, 4); + + QGridLayout *options_layout = new QGridLayout(options_group); + + int opt_row = 0; + + export_bkg_box_ = new QCheckBox(tr("Run In Background")); + export_bkg_box_->setToolTip(tr("Exporting in the background allows you to continue using Olive while " + "exporting, but may result in slower export speeds, and may" + "severely impact editing and playback performance.")); + options_layout->addWidget(export_bkg_box_, opt_row, 0); + + import_file_after_export_ = new QCheckBox(tr("Import Result After Export")); + options_layout->addWidget(import_file_after_export_, opt_row, 1); + + connect(export_bkg_box_, &QCheckBox::toggled, import_file_after_export_, [this](bool e){ + import_file_after_export_->setEnabled(!e); + }); + } + + row++; + QHBoxLayout *btn_layout = new QHBoxLayout(); btn_layout->setMargin(0); preferences_layout->addLayout(btn_layout, row, 0, 1, 4); @@ -171,12 +195,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode, QWi btn_layout->addWidget(cancel_btn); connect(cancel_btn, &QPushButton::clicked, this, &ExportDialog::reject); - export_bkg_box_ = new QCheckBox(tr("Run In Background")); - export_bkg_box_->setToolTip(tr("Exporting in the background allows you to continue using Olive while " - "exporting, but may result in slower export speeds, and may" - "severely impact editing and playback performance.")); - btn_layout->addWidget(export_bkg_box_); - btn_layout->addStretch(); splitter->addWidget(preferences_area_); @@ -371,6 +389,11 @@ void ExportDialog::ExportFinished() // If this task was cancelled, we stay open so the user can potentially queue another export } else { // Accept this dialog and close + if (import_file_after_export_) { + QString filename = filename_edit_->text().trimmed(); + emit RequestImportFile(filename); + } + this->accept(); } } diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 2b257cec2..307e8b0d4 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -67,6 +67,9 @@ public: public slots: virtual void done(int r) override; +signals: + void RequestImportFile(const QString &s); + private: void AddPreferencesTab(QWidget *inner_widget, const QString &title); @@ -118,6 +121,7 @@ private: QWidget* preferences_area_; QCheckBox *export_bkg_box_; + QCheckBox *import_file_after_export_; bool stills_only_mode_;