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] 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")) {