render: further simplified job times
Outside of silly mistakes, this should be significantly faster and more stable.
This commit is contained in:
@@ -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<sz;i++) {
|
||||
TimeRange& compare = array_[i];
|
||||
|
||||
if (remove.Contains(compare)) {
|
||||
// This element is entirely encompassed in this range, remove it
|
||||
array_.removeAt(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
|
||||
TimeRange new_range(remove.out(), compare.out());
|
||||
compare.set_out(remove.in());
|
||||
insert(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());
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -81,6 +81,36 @@ public:
|
||||
|
||||
void remove(const TimeRange& remove);
|
||||
|
||||
template <typename T>
|
||||
static void util_remove(QVector<T> *list, const TimeRange &remove)
|
||||
{
|
||||
int sz = list->size();
|
||||
|
||||
for (int i=0;i<sz;i++) {
|
||||
T& compare = (*list)[i];
|
||||
|
||||
if (remove.Contains(compare)) {
|
||||
// This element is entirely encompassed in this range, remove it
|
||||
list->removeAt(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<rational> ToVector() const
|
||||
{
|
||||
TimeRangeListFrameIterator copy(list_, timebase_);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<TimeRange> 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<TimeRange> AudioPlaybackCache::GetValidRanges(const TimeRange& range, const JobTime& job_time)
|
||||
{
|
||||
QList<TimeRange> 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);
|
||||
|
||||
@@ -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<TimeRange> GetValidRanges(const TimeRange &range, const JobTime &job_time);
|
||||
void WriteSilence(const TimeRange &range);
|
||||
|
||||
class Segment
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<jobs_.size();i++) {
|
||||
JobIdentifier& job = jobs_[i];
|
||||
TimeRange& compare = job.range;
|
||||
|
||||
if (remove.Contains(compare)) {
|
||||
// This element is entirely encompassed in this range, remove it
|
||||
jobs_.removeAt(i);
|
||||
i--;
|
||||
} else if (compare.Contains(remove, false, false)) {
|
||||
// The remove range is within this element, only choice is to split the element into two
|
||||
jobs_.append({TimeRange(remove.out(), compare.out()), job.job_time});
|
||||
compare.set_out(remove.in());
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString PlaybackCache::GetCacheDirectory() const
|
||||
{
|
||||
Project* project = GetProject();
|
||||
|
||||
@@ -92,16 +92,7 @@ protected:
|
||||
|
||||
Project* GetProject() const;
|
||||
|
||||
struct JobIdentifier {
|
||||
TimeRange range;
|
||||
JobTime job_time;
|
||||
};
|
||||
|
||||
QList<JobIdentifier> jobs_;
|
||||
|
||||
private:
|
||||
void RemoveRangeFromJobs(const TimeRange& remove);
|
||||
|
||||
TimeRangeList invalidated_;
|
||||
|
||||
rational length_;
|
||||
|
||||
@@ -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<rational> ×, JobTime job_time)
|
||||
QVector<PreviewAutoCacher::HashData> PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> ×)
|
||||
{
|
||||
std::vector<QByteArray> existing_hashes;
|
||||
QVector<HashData> hash_data(times.size());
|
||||
|
||||
QVector<QByteArray> existing_hashes;
|
||||
|
||||
for (int i=0; i<times.size(); i++) {
|
||||
const rational &time = times.at(i);
|
||||
|
||||
foreach (const rational& time, times) {
|
||||
// See if hash already exists in disk cache
|
||||
QByteArray hash = RenderManager::Hash(viewer->GetConnectedTextureOutput(), 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<rational> 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<QFuture<void> > threads;
|
||||
for (int i=0; i<times.size(); i+=hashes_per_thread) {
|
||||
threads.append(QtConcurrent::run(GenerateHashesInternal, viewer, cache, times.mid(i, i == times.size() - 1 ? -1 : hashes_per_thread), job_time));
|
||||
}
|
||||
|
||||
for (int i=0; i<threads.size(); i++) {
|
||||
threads[i].waitForFinished();
|
||||
}
|
||||
}
|
||||
return hash_data;
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::VideoInvalidated(const TimeRange &range)
|
||||
@@ -128,6 +100,7 @@ void PreviewAutoCacher::VideoInvalidated(const TimeRange &range)
|
||||
ignore_next_mouse_button_ = false;
|
||||
|
||||
invalidated_video_.insert(range);
|
||||
video_job_tracker_.insert(range, graph_changed_time_);
|
||||
|
||||
TryRender();
|
||||
}
|
||||
@@ -135,7 +108,9 @@ void PreviewAutoCacher::VideoInvalidated(const TimeRange &range)
|
||||
|
||||
void PreviewAutoCacher::AudioInvalidated(const TimeRange &range)
|
||||
{
|
||||
// ClearAudioQueue();
|
||||
// ClearAudioQueue();
|
||||
|
||||
audio_job_tracker_.insert(range, graph_changed_time_);
|
||||
|
||||
// Start jobs to re-render the audio at this range, split into 2 second chunks
|
||||
invalidated_audio_.insert(range);
|
||||
@@ -145,14 +120,28 @@ void PreviewAutoCacher::AudioInvalidated(const TimeRange &range)
|
||||
|
||||
void PreviewAutoCacher::HashesProcessed()
|
||||
{
|
||||
QFutureWatcher<void>* watcher = static_cast<QFutureWatcher<void>*>(sender());
|
||||
QFutureWatcher< QVector<HashData> >* watcher = static_cast<QFutureWatcher<QVector<HashData> >*>(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<JobTime>();
|
||||
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<JobTime>();
|
||||
|
||||
TimeRangeList valid_ranges = audio_job_tracker_.getCurrentSubRanges(range, watcher_job_time);
|
||||
|
||||
AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value<AudioVisualWaveform>();
|
||||
|
||||
viewer_node_->audio_playback_cache()->WritePCM(range,
|
||||
valid_ranges,
|
||||
watcher->Get().value<SampleBufferPtr>(),
|
||||
&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<TimeRange> 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<FramePtr>();
|
||||
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<void>* watcher = new QFutureWatcher<void>();
|
||||
hash_tasks_.append(watcher);
|
||||
connect(watcher, &QFutureWatcher<void>::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; i<QThread::idealThreadCount(); i++) {
|
||||
QueueNextHashTask();
|
||||
}
|
||||
|
||||
invalidated_video_.clear();
|
||||
}
|
||||
@@ -516,6 +510,7 @@ void PreviewAutoCacher::TryRender()
|
||||
|
||||
foreach (const TimeRange& r, chunks) {
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
watcher->setProperty("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<rational> times(kMaxFrames);
|
||||
for (int i=0; i<kMaxFrames; i++) {
|
||||
rational r;
|
||||
if (hash_iterator_.GetNext(&r)) {
|
||||
times[i] = r;
|
||||
} else {
|
||||
times.resize(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QFutureWatcher< QVector<HashData> >* watcher = new QFutureWatcher< QVector<HashData> >();
|
||||
watcher->setProperty("job", QVariant::fromValue(last_update_time_));
|
||||
hash_tasks_.append(watcher);
|
||||
connect(watcher, &QFutureWatcher< QVector<HashData> >::finished, this, &PreviewAutoCacher::HashesProcessed);
|
||||
watcher->setFuture(QtConcurrent::run(PreviewAutoCacher::GenerateHashes,
|
||||
copied_viewer_node_,
|
||||
viewer_node_->video_frame_cache(),
|
||||
times));
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void PreviewAutoCacher::ClearQueueInternal(T& list, bool hard, Func member)
|
||||
{
|
||||
|
||||
@@ -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<RenderTicketWatcher*>::iterator it);
|
||||
|
||||
void QueueNextFrameInRange(int max);
|
||||
TimeRangeListFrameIterator queued_frame_iterator_;
|
||||
void QueueNextHashTask();
|
||||
|
||||
struct HashData {
|
||||
rational time;
|
||||
QByteArray hash;
|
||||
bool exists;
|
||||
};
|
||||
|
||||
static QVector<HashData> GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> ×);
|
||||
|
||||
class QueuedJob {
|
||||
public:
|
||||
@@ -158,12 +166,13 @@ private:
|
||||
|
||||
RenderTicketPtr single_frame_render_;
|
||||
|
||||
QList<QFutureWatcher<void>*> hash_tasks_;
|
||||
QList<QFutureWatcher< QVector<HashData> >*> hash_tasks_;
|
||||
QMap<RenderTicketWatcher*, TimeRange> audio_tasks_;
|
||||
QMap<RenderTicketWatcher*, QByteArray> video_tasks_;
|
||||
QMap<RenderTicketWatcher*, QByteArray> video_download_tasks_;
|
||||
QMap<RenderTicketWatcher*, QVector<RenderTicketPtr> > 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
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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<TimeRangeWithJob> jobs_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // RENDERJOBTRACKER_H
|
||||
@@ -27,7 +27,6 @@ RenderTicket::RenderTicket() :
|
||||
has_result_(false),
|
||||
finish_count_(0)
|
||||
{
|
||||
SetJobTime();
|
||||
}
|
||||
|
||||
void RenderTicket::WaitForFinished(QMutex *mutex)
|
||||
|
||||
@@ -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<RenderTicket>;
|
||||
|
||||
Reference in New Issue
Block a user