Files
oak-editor/app/render/previewautocacher.cpp
T
itsmattkc 81c1922911 use strings to connect nodes
This makes the node system somewhat more high-level with the intent of making
working with them far more flexible and stable. By making the architecture more
abstracted, it becomes far less rigid which should allow us to do even more
with it and make it much less crash prone.
2021-02-09 15:48:37 +11:00

727 lines
22 KiB
C++

#include "previewautocacher.h"
#include <QApplication>
#include <QtConcurrent/QtConcurrent>
#include "project/item/sequence/sequence.h"
#include "project/project.h"
#include "render/rendermanager.h"
#include "render/renderprocessor.h"
namespace olive {
PreviewAutoCacher::PreviewAutoCacher() :
viewer_node_(nullptr),
paused_(false),
has_changed_(false),
use_custom_range_(false),
single_frame_render_(nullptr),
last_update_time_(0),
ignore_next_mouse_button_(false),
color_manager_(nullptr)
{
// Set default autocache range
SetPlayhead(rational());
delayed_requeue_timer_.setInterval(Config::Current()[QStringLiteral("AutoCacheDelay")].toInt());
delayed_requeue_timer_.setSingleShot(true);
connect(&delayed_requeue_timer_, &QTimer::timeout, this, &PreviewAutoCacher::RequeueFrames);
}
RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t)
{
if (single_frame_render_) {
single_frame_render_->Cancel();
}
single_frame_render_ = std::make_shared<RenderTicket>();
single_frame_render_->setProperty("time", QVariant::fromValue(t));
// Copy because TryRender() might set this to null and we still want to return a handle to this
RenderTicketPtr copy = single_frame_render_;
TryRender();
return copy;
}
void PreviewAutoCacher::SetPaused(bool paused)
{
paused_ = paused;
if (paused_) {
// Pause the autocache
ClearVideoQueue();
} else {
// Unpause the cache
RequeueFrames();
}
}
void PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> &times, qint64 job_time)
{
std::vector<QByteArray> existing_hashes;
foreach (const rational& time, times) {
// See if hash already exists in disk cache
QByteArray hash = RenderManager::Hash(viewer->GetConnectedNode(ViewerOutput::kTextureInput), viewer->video_params(), time);
// Check memory list since disk checking is slow
bool hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), hash) != existing_hashes.end());
if (!hash_exists) {
hash_exists = QFileInfo::exists(cache->CachePathName(hash));
if (hash_exists) {
existing_hashes.push_back(hash);
}
}
// 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),
Q_ARG(qint64, job_time),
Q_ARG(bool, hash_exists));
}
}
void PreviewAutoCacher::VideoInvalidated(const TimeRange &range)
{
ClearVideoQueue();
// Hash these frames since that should be relatively quick.
if (ignore_next_mouse_button_ || !(qApp->mouseButtons() & Qt::LeftButton)) {
ignore_next_mouse_button_ = false;
invalidated_video_.insert(range);
TryRender();
}
}
void PreviewAutoCacher::AudioInvalidated(const TimeRange &range)
{
ClearAudioQueue();
// Start jobs to re-render the audio at this range, split into 2 second chunks
invalidated_audio_.insert(range);
TryRender();
}
void PreviewAutoCacher::HashesProcessed()
{
QFutureWatcher<void>* watcher = static_cast<QFutureWatcher<void>*>(sender());
if (hash_tasks_.contains(watcher)) {
hash_tasks_.removeOne(watcher);
// Restart delayed requeue timer
delayed_requeue_timer_.stop();
delayed_requeue_timer_.start();
}
// The cacher might be waiting for this job to finish
if (!graph_update_queue_.isEmpty()) {
TryRender();
}
delete watcher;
}
void PreviewAutoCacher::AudioRendered()
{
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
if (audio_tasks_.contains(watcher)) {
if (!watcher->WasCancelled()) {
viewer_node_->audio_playback_cache()->WritePCM(audio_tasks_.value(watcher),
watcher->Get().value<SampleBufferPtr>(),
watcher->GetTicket()->GetJobTime());
// Retrieve visual waveforms
QVector<RenderProcessor::RenderedWaveform> waveform_list = watcher->GetTicket()->property("waveforms").value< QVector<RenderProcessor::RenderedWaveform> >();
foreach (const RenderProcessor::RenderedWaveform& waveform_info, waveform_list) {
// Find original track
Track* track = nullptr;
for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) {
if (it.value() == waveform_info.track) {
track = static_cast<Track*>(it.key());
break;
}
}
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_->audio_params().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();
}
}
}
}
audio_tasks_.remove(watcher);
}
// The cacher might be waiting for this job to finish
if (!graph_update_queue_.isEmpty()) {
TryRender();
}
delete watcher;
}
void PreviewAutoCacher::VideoRendered()
{
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
if (video_tasks_.contains(watcher)) {
if (watcher->WasCancelled()) {
// We didn't get this hash
currently_caching_hashes_.removeOne(watcher->property("hash").toByteArray());
} else {
const QByteArray& hash = video_tasks_.value(watcher);
// Download frame in another thread
RenderTicketWatcher* w = new RenderTicketWatcher();
video_download_tasks_.insert(w, hash);
connect(w, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoDownloaded);
w->SetTicket(RenderManager::instance()->SaveFrameToCache(viewer_node_->video_frame_cache(),
watcher->Get().value<FramePtr>(),
hash,
true));
}
video_tasks_.remove(watcher);
}
// The cacher might be waiting for this job to finish
if (!graph_update_queue_.isEmpty()) {
TryRender();
}
delete watcher;
}
void PreviewAutoCacher::VideoDownloaded()
{
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
if (video_download_tasks_.contains(watcher)) {
if (!watcher->WasCancelled()) {
if (watcher->Get().toBool()) {
const QByteArray& hash = video_download_tasks_.value(watcher);
currently_caching_hashes_.removeOne(hash);
viewer_node_->video_frame_cache()->ValidateFramesWithHash(hash);
} else {
qCritical() << "Failed to download video frame";
}
}
video_download_tasks_.remove(watcher);
}
delete watcher;
}
void PreviewAutoCacher::SingleFrameFinished()
{
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
RenderTicketPtr passthrough = watcher->property("passthrough").value<RenderTicketPtr>();
passthrough->Finish(watcher->GetTicket()->Get(), watcher->GetTicket()->WasCancelled());
delete watcher;
}
void PreviewAutoCacher::ProcessUpdateQueue()
{
foreach (const QueuedJob& job, graph_update_queue_) {
switch (job.type) {
case QueuedJob::kNodeAdded:
AddNode(job.node);
break;
case QueuedJob::kNodeRemoved:
RemoveNode(job.node);
break;
case QueuedJob::kEdgeAdded:
AddEdge(job.output, job.input);
break;
case QueuedJob::kEdgeRemoved:
RemoveEdge(job.output, job.input);
break;
case QueuedJob::kValueChanged:
CopyValue(job.input);
break;
case QueuedJob::kVideoParamsChanged:
UpdateVideoParams();
break;
case QueuedJob::kAudioParamsChanged:
UpdateAudioParams();
break;
}
}
graph_update_queue_.clear();
last_update_time_ = QDateTime::currentMSecsSinceEpoch();
}
bool PreviewAutoCacher::HasActiveJobs() const
{
return !hash_tasks_.isEmpty()
|| !audio_tasks_.isEmpty()
|| !video_tasks_.isEmpty();
}
void PreviewAutoCacher::AddNode(Node *node)
{
// Copy node
Node* copy = node->copy();
// Insert into map
copy_map_.insert(node, copy);
// Copy parameters
Node::CopyInputs(node, copy, false);
}
void PreviewAutoCacher::RemoveNode(Node *node)
{
// Find our copy and remove it
Node* copy = copy_map_.take(node);
// Delete it
delete copy;
}
void PreviewAutoCacher::AddEdge(const NodeOutput &output, const NodeInput &input)
{
Node* our_output = copy_map_.value(output.node());
Node* our_input = copy_map_.value(input.node());
Node::ConnectEdge(NodeOutput(our_output, output.output()), NodeInput(our_input, input.input(), input.element()));
}
void PreviewAutoCacher::RemoveEdge(const NodeOutput &output, const NodeInput &input)
{
Node* our_output = copy_map_.value(output.node());
Node* our_input = copy_map_.value(input.node());
Node::DisconnectEdge(NodeOutput(our_output, output.output()), NodeInput(our_input, input.input(), input.element()));
}
void PreviewAutoCacher::CopyValue(const NodeInput &input)
{
Node* our_input = copy_map_.value(input.node());
Node::CopyValuesOfElement(input.node(), our_input, input.input(), input.element());
}
void PreviewAutoCacher::UpdateVideoParams()
{
copied_viewer_node_->set_video_params(viewer_node_->video_params());
}
void PreviewAutoCacher::UpdateAudioParams()
{
copied_viewer_node_->set_audio_params(viewer_node_->audio_params());
}
void PreviewAutoCacher::SetPlayhead(const rational &playhead)
{
cache_range_ = TimeRange(playhead - Config::Current()["DiskCacheBehind"].value<rational>(),
playhead + Config::Current()["DiskCacheAhead"].value<rational>());
has_changed_ = true;
use_custom_range_ = false;
RequeueFrames();
}
void PreviewAutoCacher::ClearQueue(bool wait)
{
ClearHashQueue(wait);
ClearVideoQueue(wait);
ClearAudioQueue(wait);
}
void PreviewAutoCacher::ClearHashQueue(bool wait)
{
auto copy = hash_tasks_;
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
(*it)->cancel();
}
if (wait) {
copy = hash_tasks_;
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
(*it)->waitForFinished();
}
}
}
void PreviewAutoCacher::ClearVideoQueue(bool wait)
{
// Copy because tasks that cancel immediately will be automatically removed from the list
auto copy = video_tasks_;
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
it.key()->Cancel();
}
if (wait) {
copy = video_tasks_;
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
it.key()->WaitForFinished();
}
}
has_changed_ = true;
use_custom_range_ = false;
}
void PreviewAutoCacher::ClearAudioQueue(bool wait)
{
// Create a copy because otherwise
auto copy = audio_tasks_;
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
it.key()->Cancel();
}
if (wait) {
copy = audio_tasks_;
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
it.key()->WaitForFinished();
}
}
}
void PreviewAutoCacher::ClearVideoDownloadQueue(bool wait)
{
// Create a copy because otherwise
auto copy = video_download_tasks_;
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
it.key()->Cancel();
}
if (wait) {
copy = video_download_tasks_;
for (auto it=copy.cbegin(); it!=copy.cend(); it++) {
it.key()->WaitForFinished();
}
}
}
void PreviewAutoCacher::NodeAdded(Node *node)
{
graph_update_queue_.append({QueuedJob::kNodeAdded, node, NodeInput(), NodeOutput()});
}
void PreviewAutoCacher::NodeRemoved(Node *node)
{
graph_update_queue_.append({QueuedJob::kNodeRemoved, node, NodeInput(), NodeOutput()});
}
void PreviewAutoCacher::EdgeAdded(const NodeOutput &output, const NodeInput &input)
{
graph_update_queue_.append({QueuedJob::kEdgeAdded, nullptr, input, output});
}
void PreviewAutoCacher::EdgeRemoved(const NodeOutput &output, const NodeInput &input)
{
graph_update_queue_.append({QueuedJob::kEdgeRemoved, nullptr, input, output});
}
void PreviewAutoCacher::ValueChanged(const NodeInput &input)
{
graph_update_queue_.append({QueuedJob::kValueChanged, nullptr, input, NodeOutput()});
}
void PreviewAutoCacher::VideoParamsChanged()
{
// In case the user is pressing the mouse at this exact moment
IgnoreNextMouseButton();
graph_update_queue_.append({QueuedJob::kVideoParamsChanged, nullptr, NodeInput(), NodeOutput()});
ClearVideoQueue();
TryRender();
}
void PreviewAutoCacher::AudioParamsChanged()
{
graph_update_queue_.append({QueuedJob::kAudioParamsChanged, nullptr, NodeInput(), NodeOutput()});
ClearAudioQueue();
TryRender();
}
void PreviewAutoCacher::TryRender()
{
if (!graph_update_queue_.isEmpty()) {
if (HasActiveJobs()) {
// Still waiting for jobs to finish
return;
}
// No jobs are active, we can process the update queue
ProcessUpdateQueue();
}
// If we're here, we must be able to render
if (!invalidated_video_.isEmpty()) {
QVector<rational> frames = viewer_node_->video_frame_cache()->GetFrameListFromTimeRange(invalidated_video_);
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_));
invalidated_video_.clear();
}
if (!invalidated_audio_.isEmpty()) {
foreach (const TimeRange& range, invalidated_audio_) {
std::list<TimeRange> chunks = range.Split(2);
foreach (const TimeRange& r, chunks) {
RenderTicketWatcher* watcher = new RenderTicketWatcher();
connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered);
audio_tasks_.insert(watcher, r);
watcher->SetTicket(RenderManager::instance()->RenderAudio(copied_viewer_node_, r, true));
}
}
invalidated_audio_.clear();
}
if (single_frame_render_) {
RenderTicketWatcher* watcher = new RenderTicketWatcher();
watcher->setProperty("passthrough", QVariant::fromValue(single_frame_render_));
connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::SingleFrameFinished);
single_frame_render_->Start();
watcher->SetTicket(RenderManager::instance()->RenderFrame(copied_viewer_node_,
color_manager_,
single_frame_render_->property("time").value<rational>(),
RenderMode::kOffline,
viewer_node_->video_frame_cache(),
true));
single_frame_render_ = nullptr;
}
}
void PreviewAutoCacher::RequeueFrames()
{
delayed_requeue_timer_.stop();
if (viewer_node_
&& viewer_node_->video_frame_cache()->HasInvalidatedRanges()
&& hash_tasks_.isEmpty()
&& has_changed_
&& (!paused_ || use_custom_range_)) {
TimeRange using_range;
if (use_custom_range_) {
using_range = custom_autocache_range_;
use_custom_range_ = false;
} else {
using_range = cache_range_;
}
QVector<rational> invalidated_ranges = viewer_node_->video_frame_cache()->GetInvalidatedFrames(using_range);
ClearVideoQueue();
foreach (const rational& t, invalidated_ranges) {
const QByteArray& hash = viewer_node_->video_frame_cache()->GetHash(t);
if (t >= using_range.in()
&& t < using_range.out()
&& !currently_caching_hashes_.contains(hash)) {
// Don't render any hash more than once
currently_caching_hashes_.append(hash);
RenderTicketWatcher* watcher = new RenderTicketWatcher();
watcher->setProperty("hash", hash);
connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered);
video_tasks_.insert(watcher, hash);
watcher->SetTicket(RenderManager::instance()->RenderFrame(copied_viewer_node_,
color_manager_,
t,
RenderMode::kOffline,
viewer_node_->video_frame_cache(),
false));
}
}
has_changed_ = false;
}
}
void PreviewAutoCacher::IgnoreNextMouseButton()
{
ignore_next_mouse_button_ = true;
}
void PreviewAutoCacher::ForceCacheRange(const TimeRange &range)
{
has_changed_ = true;
use_custom_range_ = true;
custom_autocache_range_ = range;
RequeueFrames();
}
void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
{
if (viewer_node_ == viewer_node) {
return;
}
if (viewer_node_) {
// Cancel any remaining tickets and wait for them to finish
ClearQueue(true);
// Clear autocache lists
{
// We need to wait for these since they work directly on the FrameHashCache. Most of the time
// this is fine, but not if the FrameHashCache gets deleted after this function.
ClearHashQueue(true);
// This can be cleared normally (frames will be discarded and need to be rendered again)
ClearVideoQueue(false);
// This can be cleared normally (PCM data will be discarded and need to be rendered again)
ClearAudioQueue(false);
// We'll need to wait for these since they work directly on the FrameHashCache. Frames will
// be in the cache for later use.
ClearVideoDownloadQueue(true);
// No longer caching any hashes
currently_caching_hashes_.clear();
}
// Delete all of our copied nodes
foreach (Node* c, copy_map_) {
delete c;
}
copy_map_.clear();
copied_viewer_node_ = nullptr;
graph_update_queue_.clear();
// Disconnect signals for future node additions/deletions
NodeGraph* graph = viewer_node_->parent();
disconnect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded);
disconnect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved);
disconnect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded);
disconnect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved);
disconnect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged);
// Disconnect signal (will be a no-op if the signal was never connected)
disconnect(viewer_node_,
&ViewerOutput::VideoParamsChanged,
this,
&PreviewAutoCacher::VideoParamsChanged);
disconnect(viewer_node_,
&ViewerOutput::AudioParamsChanged,
this,
&PreviewAutoCacher::AudioParamsChanged);
disconnect(viewer_node_->video_frame_cache(),
&PlaybackCache::Invalidated,
this,
&PreviewAutoCacher::VideoInvalidated);
disconnect(viewer_node_->audio_playback_cache(),
&PlaybackCache::Invalidated,
this,
&PreviewAutoCacher::AudioInvalidated);
}
viewer_node_ = viewer_node;
if (viewer_node_) {
// Copy graph
NodeGraph* graph = viewer_node_->parent();
// Add all nodes
foreach (Node* node, graph->nodes()) {
AddNode(node);
}
// Find copied viewer node
copied_viewer_node_ = static_cast<ViewerOutput*>(copy_map_.value(viewer_node_));
// Copy parameters
copied_viewer_node_->set_video_params(viewer_node_->video_params());
copied_viewer_node_->set_audio_params(viewer_node_->audio_params());
// Add all connections
foreach (Node* node, graph->nodes()) {
for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
AddEdge(it->second, it->first);
}
}
last_update_time_ = QDateTime::currentMSecsSinceEpoch();
// Connect signals for future node additions/deletions
connect(graph, &NodeGraph::NodeAdded, this, &PreviewAutoCacher::NodeAdded);
connect(graph, &NodeGraph::NodeRemoved, this, &PreviewAutoCacher::NodeRemoved);
connect(graph, &NodeGraph::InputConnected, this, &PreviewAutoCacher::EdgeAdded);
connect(graph, &NodeGraph::InputDisconnected, this, &PreviewAutoCacher::EdgeRemoved);
connect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged);
// Copy invalidated ranges - used to determine which frames need hashing
invalidated_video_ = viewer_node_->video_frame_cache()->GetInvalidatedRanges();
invalidated_audio_ = viewer_node_->audio_playback_cache()->GetInvalidatedRanges();
// We begin an operation and never end it which prevents the copy from unnecessarily
// invalidating its own cache
copied_viewer_node_->BeginOperation();
connect(viewer_node_,
&ViewerOutput::VideoParamsChanged,
this,
&PreviewAutoCacher::VideoParamsChanged);
connect(viewer_node_,
&ViewerOutput::AudioParamsChanged,
this,
&PreviewAutoCacher::AudioParamsChanged);
connect(viewer_node_->video_frame_cache(),
&PlaybackCache::Invalidated,
this,
&PreviewAutoCacher::VideoInvalidated);
connect(viewer_node_->audio_playback_cache(),
&PlaybackCache::Invalidated,
this,
&PreviewAutoCacher::AudioInvalidated);
TryRender();
}
}
}