render: further simplified job times

Outside of silly mistakes, this should be significantly faster and more stable.
This commit is contained in:
itsmattkc
2021-07-12 15:14:37 -07:00
parent dc34f80386
commit c374a03e9d
15 changed files with 300 additions and 190 deletions
+95 -67
View File
@@ -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> &times, JobTime job_time)
QVector<PreviewAutoCacher::HashData> PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> &times)
{
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)
{