From 7f796bd99f55cfb6223023e3566d4d7283c40c7d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 3 Jan 2020 15:50:43 +1100 Subject: [PATCH] revised video renderer's invalidate cache to use ranges rather than discrete frames Previously, when the video renderer received a dirty cache signal, it would proceed to extract all frames from the range and queue them. However, this could be extremely slow for long ranges since it had to iterate through the entire range and calculate the individual frames it contained. Now, we use the same range combining system as audio and automatically calculate the next frame within the range only when necessary. Essentially the same work, but split up over time and done only when needed leading to no discernible UI pause when invalidating cache. --- app/common/timecodefunctions.cpp | 8 ++ app/common/timecodefunctions.h | 2 + app/common/timerange.cpp | 29 +++++- app/common/timerange.h | 4 +- app/render/backend/audiorenderbackend.cpp | 21 ---- app/render/backend/audiorenderbackend.h | 3 - app/render/backend/renderbackend.cpp | 42 ++++++-- app/render/backend/renderbackend.h | 10 +- app/render/backend/videorenderbackend.cpp | 120 +++++++--------------- app/render/backend/videorenderbackend.h | 7 +- 10 files changed, 118 insertions(+), 128 deletions(-) diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index 8d1947f8f..c2725290b 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -231,6 +231,14 @@ err_fatal: return 0; } +rational Timecode::snap_time_to_timebase(const rational &time, const rational &timebase) +{ + // Just convert to a timestamp in timebase units and back + int64_t timestamp = time_to_timestamp(time, timebase); + + return timestamp_to_time(timestamp, timebase); +} + rational Timecode::timestamp_to_time(const int64_t ×tamp, const rational &timebase) { return rational(timestamp) * timebase; diff --git a/app/common/timecodefunctions.h b/app/common/timecodefunctions.h index a57be0f94..e9bb4d35d 100644 --- a/app/common/timecodefunctions.h +++ b/app/common/timecodefunctions.h @@ -55,6 +55,8 @@ public: static int64_t timecode_to_timestamp(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr); + static rational snap_time_to_timebase(const rational& time, const rational& timebase); + static int64_t time_to_timestamp(const rational& time, const rational& timebase); static int64_t time_to_timestamp(const double& time, const rational& timebase); diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index fc8d96c7d..3a2564057 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -58,9 +58,13 @@ TimeRange TimeRange::CombineWith(const TimeRange &a) const return Combine(a, *this); } -bool TimeRange::Contains(const TimeRange &a) const +bool TimeRange::Contains(const TimeRange &compare, bool inout_inclusive) const { - return (a.in() >= in() && a.out() <= out()); + if (inout_inclusive) { + return (compare.in() >= in() && compare.out() <= out()); + } else { + return (compare.in() > in() && compare.out() < out()); + } } bool TimeRange::Overlap(const TimeRange &a, const TimeRange &b) @@ -114,7 +118,7 @@ void TimeRangeList::RemoveTimeRange(const TimeRange &range) // This element is entirely encompassed in this range, remove it removeAt(i); i--; - } else if (compare.Contains(range)) { + } else if (compare.Contains(range, false)) { // The remove range is within this element, only choice is to split the element into two TimeRange first(compare.in(), range.in()); TimeRange last(range.out(), compare.out()); @@ -123,10 +127,25 @@ void TimeRangeList::RemoveTimeRange(const TimeRange &range) append(last); } else if (compare.in() < range.in() && compare.out() > range.in()) { // This element's out point overlaps the range's in, we'll trim it - (*this)[i].set_out(range.in()); + TimeRange trimmed = compare; + trimmed.set_out(range.in()); + replace(i, trimmed); } else if (compare.in() < range.out() && compare.out() > range.out()) { // This element's in point overlaps the range's out, we'll trim it - (*this)[i].set_in(range.out()); + TimeRange trimmed = compare; + trimmed.set_in(range.out()); + replace(i, trimmed); } } } + +bool TimeRangeList::ContainsTimeRange(const TimeRange &range) const +{ + for (int i=0;i& threads(); @@ -98,6 +100,9 @@ protected: QHash render_job_info_; +protected slots: + void QueueRecompile(); + private: bool AllProcessorsAreAvailable() const; @@ -136,9 +141,6 @@ private: QVector processor_busy_state_; -private slots: - void QueueRecompile(); - }; #endif // RENDERBACKEND_H diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 140d85d8a..950ae6a92 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -37,79 +37,6 @@ VideoRenderBackend::VideoRenderBackend(QObject *parent) : { } -void VideoRenderBackend::InvalidateCache(const rational &start_range, const rational &end_range) -{ - if (!params_.is_valid()) { - return; - } - - RenderBackend::InvalidateCache(start_range, end_range); - - // Adjust range to min/max values - rational start_range_adj = qMax(rational(0), start_range); - rational end_range_adj = qMin(GetSequenceLength(), end_range); - - qDebug() << "Cache invalidated between" - << start_range_adj.toDouble() - << "and" - << end_range_adj.toDouble(); - - // Snap start_range to timebase - int64_t timestamp = Timecode::time_to_timestamp(start_range_adj, params_.time_base()); - rational true_start = Timecode::timestamp_to_time(timestamp, params_.time_base()); - - if (true_start == end_range_adj) { - // Ensure that a single frame is always rendered - end_range_adj += params_.time_base(); - } - - for (rational r=true_start;r diff) { - cache_queue_.insert(i, new_range); - added = true; - break; - } - } - - if (!added) { - cache_queue_.append(new_range); - } - } - - // Remove frames after this time code if it's changed - frame_cache_.Truncate(GetSequenceLength()); - - // Queue value update - QueueValueUpdate(); - - CacheNext(); -} - bool VideoRenderBackend::InitInternal() { cache_frame_load_buffer_.resize(PixelService::GetBufferSize(params_.format(), params_.effective_width(), params_.effective_height())); @@ -123,14 +50,16 @@ void VideoRenderBackend::CloseInternal() void VideoRenderBackend::ConnectViewer(ViewerOutput *node) { - connect(node, SIGNAL(VideoChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&))); - connect(node, SIGNAL(VideoGraphChanged()), this, SLOT(QueueRecompile())); + connect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache); + connect(node, &ViewerOutput::VideoGraphChanged, this, &VideoRenderBackend::QueueRecompile); + connect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength); } void VideoRenderBackend::DisconnectViewer(ViewerOutput *node) { - disconnect(node, SIGNAL(VideoChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&))); - disconnect(node, SIGNAL(VideoGraphChanged()), this, SLOT(QueueRecompile())); + disconnect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache); + disconnect(node, &ViewerOutput::VideoGraphChanged, this, &VideoRenderBackend::QueueRecompile); + disconnect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength); } const VideoRenderingParams &VideoRenderBackend::params() const @@ -254,6 +183,25 @@ bool VideoRenderBackend::CanRender() return params_.is_valid(); } +TimeRange VideoRenderBackend::PopNextFrameFromQueue() +{ + TimeRange range = cache_queue_.first(); + + // Snap the range to a single discrete frame + rational snapped_in = Timecode::snap_time_to_timebase(range.in(), params_.time_base()); + + // Check if the range starts earlier, in which case we should render that frame instead + if (range.in() < snapped_in) { + snapped_in -= params_.time_base(); + } + + // Remove this particular frame from the queue + cache_queue_.RemoveTimeRange(TimeRange(snapped_in, snapped_in + params_.time_base())); + + // Return the snapped frame + return TimeRange(snapped_in, snapped_in); +} + void VideoRenderBackend::ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value) { if (last_time_requested_ == path.in() || frame_cache_.TimeToHash(last_time_requested_) == hash) { @@ -265,12 +213,12 @@ void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_ { SetWorkerBusyState(static_cast(sender()), false); - if (SetFrameHash(dep, hash, job_time)) { - QList hashes_with_time = frame_cache()->FramesWithHash(hash); + SetFrameHash(dep, hash, job_time); - foreach (const rational& t, hashes_with_time) { - emit CachedTimeReady(t, job_time); - } + QList hashes_with_time = frame_cache()->FramesWithHash(hash); + + foreach (const rational& t, hashes_with_time) { + emit CachedTimeReady(t, job_time); } // Queue up a new frame for this worker @@ -302,9 +250,15 @@ void VideoRenderBackend::ThreadHashAlreadyExists(NodeDependency dep, qint64 job_ CacheNext(); } +void VideoRenderBackend::TruncateFrameCacheLength(const rational &length) +{ + // Remove frames after this time code if it's changed + frame_cache_.Truncate(length); +} + bool VideoRenderBackend::TimeIsQueued(const TimeRange &time) const { - return cache_queue_.contains(time); + return cache_queue_.ContainsTimeRange(time); } bool VideoRenderBackend::JobIsCurrent(const NodeDependency &dep, const qint64& job_time) const diff --git a/app/render/backend/videorenderbackend.h b/app/render/backend/videorenderbackend.h index e60cd388b..bb71040b4 100644 --- a/app/render/backend/videorenderbackend.h +++ b/app/render/backend/videorenderbackend.h @@ -57,9 +57,6 @@ public: bool IsRendered(const rational& time) const; -public slots: - virtual void InvalidateCache(const rational &start_range, const rational &end_range) override; - protected: /** * @brief Allocate and start the multithreaded backend @@ -86,6 +83,8 @@ protected: virtual bool CanRender() override; + virtual TimeRange PopNextFrameFromQueue() override; + VideoRenderFrameCache* frame_cache(); const VideoRenderingParams& params() const; @@ -128,6 +127,8 @@ private slots: void ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash); void ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash); + void TruncateFrameCacheLength(const rational& length); + }; #endif // VIDEORENDERERBACKEND_H