From c374a03e9d952fb9e3ad2ccab36f1ea08a8b5774 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 12 Jul 2021 15:14:37 -0700 Subject: [PATCH] render: further simplified job times Outside of silly mistakes, this should be significantly faster and more stable. --- app/common/timerange.cpp | 32 ++---- app/common/timerange.h | 32 ++++++ app/render/CMakeLists.txt | 2 + app/render/audioplaybackcache.cpp | 26 +---- app/render/audioplaybackcache.h | 6 +- app/render/framehashcache.cpp | 12 +-- app/render/framehashcache.h | 3 +- app/render/playbackcache.cpp | 32 ------ app/render/playbackcache.h | 9 -- app/render/previewautocacher.cpp | 162 ++++++++++++++++++------------ app/render/previewautocacher.h | 23 ++++- app/render/renderjobtracker.cpp | 71 +++++++++++++ app/render/renderjobtracker.h | 67 ++++++++++++ app/threading/threadticket.cpp | 1 - app/threading/threadticket.h | 12 --- 15 files changed, 300 insertions(+), 190 deletions(-) create mode 100644 app/render/renderjobtracker.cpp create mode 100644 app/render/renderjobtracker.h diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index ac04bdd88..3f0f08829 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -199,30 +199,7 @@ void TimeRangeList::insert(TimeRange range_to_add) void TimeRangeList::remove(const TimeRange &remove) { - int sz = this->size(); - - for (int i=0;i remove.in()) { - // This element's out point overlaps the range's in, we'll trim it - compare.set_out(remove.in()); - } else if (compare.in() < remove.out() && compare.out() > remove.out()) { - // This element's in point overlaps the range's out, we'll trim it - compare.set_in(remove.out()); - } - } + util_remove(&array_, remove); } bool TimeRangeList::contains(const TimeRange &range, bool in_inclusive, bool out_inclusive) const @@ -312,7 +289,7 @@ TimeRangeListFrameIterator::TimeRangeListFrameIterator(const TimeRangeList &list bool TimeRangeListFrameIterator::GetNext(rational *out) { - if (index_ == list_.size()) { + if (!HasNext()) { return false; } @@ -328,6 +305,11 @@ bool TimeRangeListFrameIterator::GetNext(rational *out) return true; } +bool TimeRangeListFrameIterator::HasNext() const +{ + return index_ < list_.size(); +} + int TimeRangeListFrameIterator::size() { if (size_ == -1) { diff --git a/app/common/timerange.h b/app/common/timerange.h index 14b12ebae..7362f6518 100644 --- a/app/common/timerange.h +++ b/app/common/timerange.h @@ -81,6 +81,36 @@ public: void remove(const TimeRange& remove); + template + static void util_remove(QVector *list, const TimeRange &remove) + { + int sz = list->size(); + + for (int i=0;iremoveAt(i); + i--; + sz--; + } else if (compare.Contains(remove, false, false)) { + // The remove range is within this element, only choice is to split the element into two + T new_range = compare; + new_range.set_in(remove.out()); + compare.set_out(remove.in()); + list->append(new_range); + break; + } else if (compare.in() < remove.in() && compare.out() > remove.in()) { + // This element's out point overlaps the range's in, we'll trim it + compare.set_out(remove.in()); + } else if (compare.in() < remove.out() && compare.out() > remove.out()) { + // This element's in point overlaps the range's out, we'll trim it + compare.set_in(remove.out()); + } + } + } + bool contains(const TimeRange& range, bool in_inclusive = true, bool out_inclusive = true) const; bool isEmpty() const @@ -156,6 +186,8 @@ public: bool GetNext(rational *out); + bool HasNext() const; + QVector ToVector() const { TimeRangeListFrameIterator copy(list_, timebase_); diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 6f6898154..122aa5422 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -46,6 +46,8 @@ set(OLIVE_SOURCES render/rendercache.h render/rendererthreadwrapper.cpp render/rendererthreadwrapper.h + render/renderjobtracker.cpp + render/renderjobtracker.h render/rendermanager.cpp render/rendermanager.h render/rendermodes.h diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index 4a83b112b..95a9ab30b 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -56,13 +56,8 @@ void AudioPlaybackCache::SetParameters(const AudioParams ¶ms) emit ParametersChanged(); } -void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr samples, const AudioVisualWaveform *waveform, const JobTime &job_time) +void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples, const AudioVisualWaveform *waveform) { - QList valid_ranges = GetValidRanges(range, job_time); - if (valid_ranges.isEmpty()) { - return; - } - // Ensure if we have enough segments to write this data, creating more if not qint64 length_diff = params_.time_to_bytes(range.out()) - playlist_.GetLength(); while (length_diff > 0) { @@ -154,11 +149,11 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr sample } } -void AudioPlaybackCache::WriteSilence(const TimeRange &range, JobTime job_time) +void AudioPlaybackCache::WriteSilence(const TimeRange &range) { // WritePCM will automatically fill non-existent bytes with silence, so we just have to send // it an empty sample buffer - WritePCM(range, nullptr, nullptr, job_time); + WritePCM(range, {range}, nullptr, nullptr); } void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational &to_in_time) @@ -391,21 +386,6 @@ void AudioPlaybackCache::UpdateOffsetsFrom(int index) } } -QList AudioPlaybackCache::GetValidRanges(const TimeRange& range, const JobTime& job_time) -{ - QList valid_ranges; - - for (int i=jobs_.size()-1;i>=0;i--) { - const JobIdentifier& job = jobs_.at(i); - - if (job_time >= job.job_time && job.range.OverlapsWith(range)) { - valid_ranges.append(job.range.Intersected(range)); - } - } - - return valid_ranges; -} - AudioPlaybackCache::PlaybackDevice *AudioPlaybackCache::CreatePlaybackDevice(QObject* parent) const { return new PlaybackDevice(playlist_, parent); diff --git a/app/render/audioplaybackcache.h b/app/render/audioplaybackcache.h index 2e0989a53..a3f26b6d1 100644 --- a/app/render/audioplaybackcache.h +++ b/app/render/audioplaybackcache.h @@ -66,11 +66,9 @@ public: void SetParameters(const AudioParams& params); - void WritePCM(const TimeRange &range, SampleBufferPtr samples, const AudioVisualWaveform *waveform, const JobTime& job_time); + void WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples, const AudioVisualWaveform *waveform); - void WriteSilence(const TimeRange &range, JobTime job_time); - - QList GetValidRanges(const TimeRange &range, const JobTime &job_time); + void WriteSilence(const TimeRange &range); class Segment { diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index 89c07ddda..4922afcba 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -65,18 +65,8 @@ QByteArray FrameHashCache::GetHash(const rational &time) return GetHash(ToTimestamp(time)); } -void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, const JobTime& job_time, bool frame_exists) +void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, bool frame_exists) { - for (int i=jobs_.size()-1; i>=0; i--) { - const JobIdentifier& job = jobs_.at(i); - - if (job.range.Contains(time) - && job_time < job.job_time) { - // Hash here has changed since this frame started rendering, discard it - return; - } - } - int64_t ts = ToTimestamp(time); if (ts >= GetMapSize()) { // Disabled: bizarrely causes the whole app to hang indefinitely when used diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index fce812fee..b95d5f977 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -67,8 +67,7 @@ public: FramePtr LoadCacheFrame(const QByteArray& hash) const; static FramePtr LoadCacheFrame(const QString& fn); -public slots: - void SetHash(const olive::rational &time, const QByteArray& hash, const olive::JobTime &job_time, bool frame_exists); + void SetHash(const olive::rational &time, const QByteArray& hash, bool frame_exists); protected: virtual void LengthChangedEvent(const rational& old, const rational& newlen) override; diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index cbdd6c53b..73cb827aa 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -36,9 +36,6 @@ void PlaybackCache::Invalidate(const TimeRange &r) invalidated_.insert(r); - RemoveRangeFromJobs(r); - jobs_.append({r, JobTime()}); - InvalidateEvent(r); emit Invalidated(r); @@ -66,15 +63,12 @@ void PlaybackCache::SetLength(const rational &r) if (r.isNull()) { invalidated_.clear(); - jobs_.clear(); } else if (r > length_) { // If new length is greater, simply extend the invalidated range for now invalidated_.insert(range_diff); - jobs_.append({range_diff, JobTime()}); } else { // If new length is smaller, removed hashes invalidated_.remove(range_diff); - RemoveRangeFromJobs(range_diff); } rational old_length = length_; @@ -110,7 +104,6 @@ void PlaybackCache::Shift(rational from, rational to) // Remove everything from the minimum point TimeRange remove_range = TimeRange(qMin(from, to), RATIONAL_MAX); - RemoveRangeFromJobs(remove_range); Validate(remove_range); // Shift invalidated ranges @@ -163,31 +156,6 @@ Project *PlaybackCache::GetProject() const return viewer->project(); } -void PlaybackCache::RemoveRangeFromJobs(const TimeRange &remove) -{ - // Code shamelessly copied from TimeRangeList::RemoveTimeRange - for (int i=0;i remove.in()) { - // This element's out point overlaps the range's in, we'll trim it - compare.set_out(remove.in()); - } else if (compare.in() < remove.out() && compare.out() > remove.out()) { - // This element's in point overlaps the range's out, we'll trim it - compare.set_in(remove.out()); - } - } -} - QString PlaybackCache::GetCacheDirectory() const { Project* project = GetProject(); diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index a23f51101..5036ccc2e 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -92,16 +92,7 @@ protected: Project* GetProject() const; - struct JobIdentifier { - TimeRange range; - JobTime job_time; - }; - - QList jobs_; - private: - void RemoveRangeFromJobs(const TimeRange& remove); - TimeRangeList invalidated_; rational length_; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 567e5ed19..ce2c4fe5d 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -19,7 +19,7 @@ PreviewAutoCacher::PreviewAutoCacher() : { paused_ = !Config::Current()[QStringLiteral("AutoCacheEnabled")].toBool(), - SetPlayhead(0); + SetPlayhead(0); delayed_requeue_timer_.setInterval(Config::Current()[QStringLiteral("AutoCacheDelay")].toInt()); delayed_requeue_timer_.setSingleShot(true); @@ -61,16 +61,20 @@ void PreviewAutoCacher::SetPaused(bool paused) paused_ = paused; } -void GenerateHashesInternal(ViewerOutput *viewer, FrameHashCache* cache, const QVector ×, JobTime job_time) +QVector PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector ×) { - std::vector existing_hashes; + QVector hash_data(times.size()); + + QVector existing_hashes; + + for (int i=0; iGetConnectedTextureOutput(), viewer->GetVideoParams(), time); // Check memory list since disk checking is slow - bool hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), hash) != existing_hashes.end()); + bool hash_exists = existing_hashes.contains(hash); if (!hash_exists) { hash_exists = QFileInfo::exists(cache->CachePathName(hash)); @@ -81,42 +85,10 @@ void GenerateHashesInternal(ViewerOutput *viewer, FrameHashCache* cache, const Q } // Set hash in FrameHashCache's thread rather than in ours to prevent race conditions - QMetaObject::invokeMethod(cache, "SetHash", Qt::QueuedConnection, - OLIVE_NS_ARG(rational, time), - Q_ARG(QByteArray, hash), - OLIVE_NS_ARG(JobTime, job_time), - Q_ARG(bool, hash_exists)); - } -} - -void PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, TimeRangeListFrameIterator iterator, JobTime job_time) -{ - QVector times = iterator.ToVector(); - - // Ensure number of threads doesn't exceed idealThreadCount for maximum concurrency - int hashes_per_thread = times.size() / qMax(1, QThread::idealThreadCount()-1); - - // Somewhat arbitrary (it felt right) number used to determine when the overhead of sending this - // to threads will exceed the benefit of multithreading - static const int kMinimumHashesPerThread = 500; - if (hashes_per_thread < kMinimumHashesPerThread) { - hashes_per_thread = kMinimumHashesPerThread; + hash_data[i] = {time, hash, hash_exists}; } - // Queue threaded tasks for each - if (hashes_per_thread >= times.size()) { - // Don't bother queuing in other thread, just run - GenerateHashesInternal(viewer, cache, times, job_time); - } else { - QVector > threads; - for (int i=0; i* watcher = static_cast*>(sender()); + QFutureWatcher< QVector >* watcher = static_cast >*>(sender()); if (hash_tasks_.contains(watcher)) { hash_tasks_.removeOne(watcher); - // Restart delayed requeue timer - delayed_requeue_timer_.stop(); - delayed_requeue_timer_.start(); + // Set all hashes we received + JobTime job_time = watcher->property("job").value(); + auto hashes = watcher->result(); + foreach (auto hash, hashes) { + if (video_job_tracker_.isCurrent(hash.time, job_time)) { + viewer_node_->video_frame_cache()->SetHash(hash.time, hash.hash, hash.exists); + } + } + + if (hash_iterator_.HasNext()) { + // Launch next hashes + QueueNextHashTask(); + } else { + // Restart delayed requeue timer + delayed_requeue_timer_.stop(); + delayed_requeue_timer_.start(); + } } // The cacher might be waiting for this job to finish @@ -170,18 +159,21 @@ void PreviewAutoCacher::AudioRendered() if (audio_tasks_.contains(watcher)) { if (watcher->HasResult()) { const TimeRange &range = audio_tasks_.value(watcher); + JobTime watcher_job_time = watcher->property("job").value(); + + TimeRangeList valid_ranges = audio_job_tracker_.getCurrentSubRanges(range, watcher_job_time); AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value(); viewer_node_->audio_playback_cache()->WritePCM(range, + valid_ranges, watcher->Get().value(), - &waveform, - watcher->GetTicket()->GetJobTime()); + &waveform); bool pcm_is_usable = true; if (watcher->GetTicket()->property("incomplete").toBool()) { - if (last_conform_task_ > watcher->GetTicket()->GetJobTime()) { + if (last_conform_task_ > watcher_job_time) { // Requeue now viewer_node_->audio_playback_cache()->Invalidate(range); pcm_is_usable = false; @@ -205,19 +197,15 @@ void PreviewAutoCacher::AudioRendered() } } - if (track) { - QList valid_ranges = viewer_node_->audio_playback_cache()->GetValidRanges(waveform_info.range, - watcher->GetTicket()->GetJobTime()); - if (!valid_ranges.isEmpty()) { - // Generate visual waveform in this background thread - track->waveform().set_channel_count(viewer_node_->GetAudioParams().channel_count()); + if (track && !valid_ranges.isEmpty()) { + // Generate visual waveform in this background thread + track->waveform().set_channel_count(viewer_node_->GetAudioParams().channel_count()); - foreach (const TimeRange& r, valid_ranges) { - track->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length()); - } - - emit track->PreviewChanged(); + foreach (const TimeRange& r, valid_ranges) { + track->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length()); } + + emit track->PreviewChanged(); } } } @@ -246,6 +234,7 @@ void PreviewAutoCacher::VideoRendered() if (!hash.isEmpty() && VideoParams::FormatIsFloat(viewer_node_->GetVideoParams().format())) { FramePtr frame = watcher->Get().value(); RenderTicketWatcher* w = new RenderTicketWatcher(); + w->setProperty("job", QVariant::fromValue(last_update_time_)); w->setProperty("frame", QVariant::fromValue(frame)); video_download_tasks_.insert(w, hash); connect(w, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoDownloaded); @@ -396,6 +385,11 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) Node::CopyInputs(node, copy, false); } +void PreviewAutoCacher::UpdateGraphChangeValue() +{ + graph_changed_time_.Acquire(); +} + void PreviewAutoCacher::UpdateLastSyncedValue() { last_update_time_.Acquire(); @@ -460,26 +454,31 @@ void PreviewAutoCacher::ClearVideoDownloadQueue(bool hard) void PreviewAutoCacher::NodeAdded(Node *node) { graph_update_queue_.append({QueuedJob::kNodeAdded, node, NodeInput(), NodeOutput()}); + UpdateGraphChangeValue(); } void PreviewAutoCacher::NodeRemoved(Node *node) { graph_update_queue_.append({QueuedJob::kNodeRemoved, node, NodeInput(), NodeOutput()}); + UpdateGraphChangeValue(); } void PreviewAutoCacher::EdgeAdded(const NodeOutput &output, const NodeInput &input) { graph_update_queue_.append({QueuedJob::kEdgeAdded, nullptr, input, output}); + UpdateGraphChangeValue(); } void PreviewAutoCacher::EdgeRemoved(const NodeOutput &output, const NodeInput &input) { graph_update_queue_.append({QueuedJob::kEdgeRemoved, nullptr, input, output}); + UpdateGraphChangeValue(); } void PreviewAutoCacher::ValueChanged(const NodeInput &input) { graph_update_queue_.append({QueuedJob::kValueChanged, nullptr, input, NodeOutput()}); + UpdateGraphChangeValue(); } void PreviewAutoCacher::TryRender() @@ -496,16 +495,11 @@ void PreviewAutoCacher::TryRender() // If we're here, we must be able to render if (!invalidated_video_.isEmpty()) { - TimeRangeListFrameIterator frames(invalidated_video_, viewer_node_->video_frame_cache()->GetTimebase()); + hash_iterator_ = TimeRangeListFrameIterator(invalidated_video_, viewer_node_->video_frame_cache()->GetTimebase()); - QFutureWatcher* watcher = new QFutureWatcher(); - hash_tasks_.append(watcher); - connect(watcher, &QFutureWatcher::finished, this, &PreviewAutoCacher::HashesProcessed); - watcher->setFuture(QtConcurrent::run(&PreviewAutoCacher::GenerateHashes, - copied_viewer_node_, - viewer_node_->video_frame_cache(), - frames, - last_update_time_)); + for (int i=0; isetProperty("job", QVariant::fromValue(last_update_time_)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); audio_tasks_.insert(watcher, r); watcher->SetTicket(RenderManager::instance()->RenderAudio(copied_viewer_node_, r, RenderMode::kOffline, true)); @@ -550,6 +545,7 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(const QByteArray &hash, cons { RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("hash", hash); + watcher->setProperty("job", QVariant::fromValue(last_update_time_)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered); video_tasks_.insert(watcher, hash); watcher->SetTicket(RenderManager::instance()->RenderFrame(copied_viewer_node_, @@ -654,6 +650,8 @@ 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(); // Disconnect signals for future node additions/deletions NodeGraph* graph = viewer_node_->parent(); @@ -701,6 +699,8 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) } } + // Ensure graph change value is just before the sync value + UpdateGraphChangeValue(); UpdateLastSyncedValue(); // Connect signals for future node additions/deletions @@ -712,7 +712,9 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) // Copy invalidated ranges - used to determine which frames need hashing invalidated_video_ = viewer_node_->video_frame_cache()->GetInvalidatedRanges(); + video_job_tracker_.insert(invalidated_video_, graph_changed_time_); invalidated_audio_ = viewer_node_->audio_playback_cache()->GetInvalidatedRanges(); + audio_job_tracker_.insert(invalidated_audio_, graph_changed_time_); connect(viewer_node_->video_frame_cache(), &PlaybackCache::Invalidated, @@ -776,6 +778,32 @@ void PreviewAutoCacher::QueueNextFrameInRange(int max) } } +void PreviewAutoCacher::QueueNextHashTask() +{ + // Magic number: dunno what the best number for this is yet + static const int kMaxFrames = 1000; + + QVector times(kMaxFrames); + for (int i=0; i >* watcher = new QFutureWatcher< QVector >(); + watcher->setProperty("job", QVariant::fromValue(last_update_time_)); + hash_tasks_.append(watcher); + connect(watcher, &QFutureWatcher< QVector >::finished, this, &PreviewAutoCacher::HashesProcessed); + watcher->setFuture(QtConcurrent::run(PreviewAutoCacher::GenerateHashes, + copied_viewer_node_, + viewer_node_->video_frame_cache(), + times)); +} + template void PreviewAutoCacher::ClearQueueInternal(T& list, bool hard, Func member) { diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index e60ca6c2a..8f865920e 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -9,6 +9,7 @@ #include "node/node.h" #include "node/output/viewer/viewer.h" #include "node/project/project.h" +#include "render/renderjobtracker.h" #include "threading/threadticketwatcher.h" namespace olive { @@ -80,8 +81,6 @@ public: void ClearVideoDownloadQueue(bool wait = false); private: - static void GenerateHashes(ViewerOutput *viewer, FrameHashCache *cache, TimeRangeListFrameIterator times, JobTime job_time); - void TryRender(); RenderTicketWatcher *RenderFrame(const QByteArray& hash, const rational &time, bool prioritize, bool texture_only); @@ -104,6 +103,7 @@ private: void InsertIntoCopyMap(Node* node, Node* copy); + void UpdateGraphChangeValue(); void UpdateLastSyncedValue(); void CancelQueuedSingleFrameRender(); @@ -116,7 +116,15 @@ private: void ClearQueueRemoveEventInternal(QVector::iterator it); void QueueNextFrameInRange(int max); - TimeRangeListFrameIterator queued_frame_iterator_; + void QueueNextHashTask(); + + struct HashData { + rational time; + QByteArray hash; + bool exists; + }; + + static QVector GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector ×); class QueuedJob { public: @@ -158,12 +166,13 @@ private: RenderTicketPtr single_frame_render_; - QList*> hash_tasks_; + QList >*> hash_tasks_; QMap audio_tasks_; QMap video_tasks_; QMap video_download_tasks_; QMap > video_immediate_passthroughs_; + JobTime graph_changed_time_; JobTime last_update_time_; bool ignore_next_mouse_button_; @@ -174,6 +183,12 @@ private: JobTime last_conform_task_; + RenderJobTracker video_job_tracker_; + RenderJobTracker audio_job_tracker_; + + TimeRangeListFrameIterator queued_frame_iterator_; + TimeRangeListFrameIterator hash_iterator_; + private slots: /** * @brief Handler for when the NodeGraph reports a video change over a certain time range diff --git a/app/render/renderjobtracker.cpp b/app/render/renderjobtracker.cpp new file mode 100644 index 000000000..29f07b3f0 --- /dev/null +++ b/app/render/renderjobtracker.cpp @@ -0,0 +1,71 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "renderjobtracker.h" + +namespace olive { + +void RenderJobTracker::insert(const TimeRange &range, JobTime job_time) +{ + // First remove any ranges with this (code copied + TimeRangeList::util_remove(&jobs_, range); + + // Now append the job + TimeRangeWithJob job(range, job_time); + jobs_.append(job); +} + +void RenderJobTracker::insert(const TimeRangeList &ranges, JobTime job_time) +{ + foreach (const TimeRange &r, ranges) { + insert(r, job_time); + } +} + +void RenderJobTracker::clear() +{ + jobs_.clear(); +} + +bool RenderJobTracker::isCurrent(const rational &time, JobTime job_time) const +{ + for (auto it=jobs_.crbegin(); it!=jobs_.crend(); it++) { + if (it->Contains(time)) { + return job_time >= it->GetJobTime(); + } + } + + return false; +} + +TimeRangeList RenderJobTracker::getCurrentSubRanges(const TimeRange &range, const JobTime &job_time) const +{ + TimeRangeList current_ranges; + + for (auto it=jobs_.crbegin(); it!=jobs_.crend(); it++) { + if (job_time >= it->GetJobTime() && it->OverlapsWith(range)) { + current_ranges.insert(it->Intersected(range)); + } + } + + return current_ranges; +} + +} diff --git a/app/render/renderjobtracker.h b/app/render/renderjobtracker.h new file mode 100644 index 000000000..169331072 --- /dev/null +++ b/app/render/renderjobtracker.h @@ -0,0 +1,67 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef RENDERJOBTRACKER_H +#define RENDERJOBTRACKER_H + +#include "common/jobtime.h" +#include "common/timerange.h" + +namespace olive { + +class RenderJobTracker +{ +public: + RenderJobTracker() = default; + + void insert(const TimeRange &range, JobTime job_time); + void insert(const TimeRangeList &ranges, JobTime job_time); + + void clear(); + + bool isCurrent(const rational &time, JobTime job_time) const; + + TimeRangeList getCurrentSubRanges(const TimeRange &range, const JobTime &job_time) const; + +private: + class TimeRangeWithJob : public TimeRange + { + public: + TimeRangeWithJob(const TimeRange &range, const JobTime &job_time) + { + set_range(range.in(), range.out()); + job_time_ = job_time; + } + + JobTime GetJobTime() const {return job_time_;} + void SetJobTime(JobTime jt) {job_time_ = jt;} + + private: + JobTime job_time_; + + }; + + QVector jobs_; + +}; + +} + +#endif // RENDERJOBTRACKER_H diff --git a/app/threading/threadticket.cpp b/app/threading/threadticket.cpp index 5dfd29d4c..d8d041d9e 100644 --- a/app/threading/threadticket.cpp +++ b/app/threading/threadticket.cpp @@ -27,7 +27,6 @@ RenderTicket::RenderTicket() : has_result_(false), finish_count_(0) { - SetJobTime(); } void RenderTicket::WaitForFinished(QMutex *mutex) diff --git a/app/threading/threadticket.h b/app/threading/threadticket.h index 82bd8b90a..d4035c832 100644 --- a/app/threading/threadticket.h +++ b/app/threading/threadticket.h @@ -38,16 +38,6 @@ class RenderTicket : public QObject public: RenderTicket(); - JobTime GetJobTime() const - { - return job_time_; - } - - void SetJobTime() - { - job_time_.Acquire(); - } - /** * @brief Get the ticket's current state * @@ -137,8 +127,6 @@ private: QWaitCondition wait_; - JobTime job_time_; - }; using RenderTicketPtr = std::shared_ptr;