Files
oak-editor/app/render/previewautocacher.cpp
T
itsmattkc ae3393f031 opengl: use shared contexts and downgrade to ES 2.0 (BIG CHANGE)
If you run into new OpenGL issues, this is likely the commit that caused it.
I've tested this on as many systems as I have access to (including AMD, Apple,
Intel, and NVIDIA GPUs with a mixture of Windows, Linux, and macOS) and it
appears to work fine, however we all know how unpredictable GPUs can be.
This should help increase GPU performance and compatibility.

Squashed commit of the following:

commit fa3c0ac7c469aebe2941a803736bdc06cdd5a2d8
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 13:17:44 2021 +1000

    viewer: updated pixel sampling for shared contexts

commit 9334261da235f09cb67b193c5bb6169abd7a95f7
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 13:12:39 2021 +1000

    pixelsampler: show color preview box

commit dacd9be6971c5c0312ae45c8db349d47910a80dd
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 12:43:32 2021 +1000

    renderer: interlace on GPU

commit 074cea06cb87e9fdc4e8ea6b09587be6df296057
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 12:43:14 2021 +1000

    renderer: added texture clear option

commit 5d4f86b4cf1b1dcd3fb8e8c854ffa7f6fb8ca346
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 12:42:12 2021 +1000

    renderer: updated scopes for shared textures

commit 6fad96358d189874cf45f7887b7e13cc2b1603c4
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 11:42:05 2021 +1000

    opengl: fixed edge case

commit 1a19ffb53703ac6f30ed210f6d8eb5fc4b7b0f2d
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 10:56:13 2021 +1000

    opengl: fix deinterlace shader

commit 085961aa39cca90ebf51b919a78ef07a4882f0e1
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 10:08:40 2021 +1000

    opengl: downgrade to ES 2.0

    With half-float/float/3D extensions, this should be doable

commit ea1ab45cbcf5e017dfd0d8f28b231ff3255dfe92
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 10:06:59 2021 +1000

    renderer: fixed mutex deadlock

commit ecc2f08b16f03421870c5b61625ecd26f2f96582
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 21:28:13 2021 +1000

    opengl: fixed shared contexts

commit 261c689ea8089463155cf11b9ab15aa3f879f439
Merge: bfd4338a 91c88b1c
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 19:52:00 2021 +1000

    Merge branch 'master' into shared-contexts

commit bfd4338aaa3b09b6fbb7717c637face8d5490721
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 18:52:38 2021 +1000

    viewer: remove debug line

commit 45868d8a396181bf81c24b92a1ac5ce9ffed3426
Merge: 642f8e9d d0bd5e21
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 17:21:48 2021 +1000

    Merge branch 'master' into shared-contexts

commit 642f8e9df7d7d1a9ed4202d428f4d968ff668552
Merge: 861e5b89 4de0a727
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 17:01:38 2021 +1000

    Merge branch 'master' of https://github.com/olive-editor/olive into shared-contexts

commit 861e5b89a38c281897dc44b20e3587d39c4f808c
Merge: 472598a7 ef31c3fb
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 14:28:21 2021 +1000

    Merge branch 'master' into shared-contexts

commit 472598a740a095aa43a4aad5680dd5abcf453a4c
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Fri May 14 16:29:35 2021 +1000

    attempt at sharing contexts
2021-05-29 20:02:45 +10:00

813 lines
25 KiB
C++

#include "previewautocacher.h"
#include <QApplication>
#include <QtConcurrent/QtConcurrent>
#include "codec/conformmanager.h"
#include "node/project/project.h"
#include "render/rendermanager.h"
#include "render/renderprocessor.h"
namespace olive {
PreviewAutoCacher::PreviewAutoCacher() :
viewer_node_(nullptr),
has_changed_(false),
use_custom_range_(false),
single_frame_render_(nullptr),
last_update_time_(0),
ignore_next_mouse_button_(false),
last_conform_task_(0)
{
paused_ = !Config::Current()[QStringLiteral("AutoCacheEnabled")].toBool(),
SetPlayhead(0);
delayed_requeue_timer_.setInterval(Config::Current()[QStringLiteral("AutoCacheDelay")].toInt());
delayed_requeue_timer_.setSingleShot(true);
connect(&delayed_requeue_timer_, &QTimer::timeout, this, &PreviewAutoCacher::RequeueFrames);
connect(ConformManager::instance(), &ConformManager::ConformReady, this, &PreviewAutoCacher::ConformFinished);
}
PreviewAutoCacher::~PreviewAutoCacher()
{
// Ensure everything is cleaned up appropriately
SetViewerNode(nullptr);
}
RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool prioritize)
{
CancelQueuedSingleFrameRender();
QByteArray hash;
if (!paused_) {
hash = viewer_node_->video_frame_cache()->GetHash(t);
}
auto sfr = std::make_shared<RenderTicket>();
sfr->Start();
sfr->setProperty("time", QVariant::fromValue(t));
sfr->setProperty("prioritize", prioritize);
sfr->setProperty("hash", hash);
// Attempt to queue
single_frame_render_ = sfr;
TryRender();
return sfr;
}
void PreviewAutoCacher::SetPaused(bool paused)
{
paused_ = paused;
}
void GenerateHashesInternal(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->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());
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::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> &times, qint64 job_time)
{
// 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;
}
// 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();
}
}
}
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->HasResult()) {
const TimeRange &range = audio_tasks_.value(watcher);
viewer_node_->audio_playback_cache()->WritePCM(range,
watcher->Get().value<SampleBufferPtr>(),
watcher->GetTicket()->GetJobTime());
bool pcm_is_usable = true;
if (watcher->GetTicket()->property("incomplete").toBool()) {
if (last_conform_task_ > watcher->GetTicket()->GetJobTime()) {
// Requeue now
viewer_node_->audio_playback_cache()->Invalidate(range, QDateTime::currentMSecsSinceEpoch());
pcm_is_usable = false;
} else {
// Wait for conform
audio_needing_conform_.insert(range);
}
}
if (pcm_is_usable) {
// 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_->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();
}
}
}
}
}
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->HasResult()) {
const QByteArray& hash = video_tasks_.value(watcher);
// Download frame in another thread
if (!hash.isEmpty() && VideoParams::FormatIsFloat(viewer_node_->GetVideoParams().format())) {
FramePtr frame = watcher->Get().value<FramePtr>();
RenderTicketWatcher* w = new RenderTicketWatcher();
w->setProperty("frame", QVariant::fromValue(frame));
video_download_tasks_.insert(w, hash);
connect(w, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoDownloaded);
w->SetTicket(RenderManager::instance()->SaveFrameToCache(viewer_node_->video_frame_cache(),
frame,
hash,
true));
}
}
video_tasks_.remove(watcher);
}
// Process passthroughs
QVector<RenderTicketPtr> tickets = video_immediate_passthroughs_.take(watcher);
foreach (RenderTicketPtr t, tickets) {
if (watcher->HasResult()) {
t->Finish(watcher->Get());
} else {
t->Finish();
}
}
// 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->HasResult()) {
if (watcher->Get().toBool()) {
const QByteArray& hash = video_download_tasks_.value(watcher);
viewer_node_->video_frame_cache()->ValidateFramesWithHash(hash);
} else {
qCritical() << "Failed to download video frame";
}
}
video_download_tasks_.remove(watcher);
}
// The cacher might be waiting for this job to finish
if (!graph_update_queue_.isEmpty()) {
TryRender();
}
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;
}
}
graph_update_queue_.clear();
UpdateLastSyncedValue();
}
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();
// Add to project
copy->setParent(&copied_project_);
// Insert into map
InsertIntoCopyMap(node, copy);
// Keep track of our nodes
created_nodes_.append(copy);
}
void PreviewAutoCacher::RemoveNode(Node *node)
{
// Find our copy and remove it
Node* copy = copy_map_.take(node);
// Remove from created list
created_nodes_.removeOne(copy);
// 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::InsertIntoCopyMap(Node *node, Node *copy)
{
// Insert into map
copy_map_.insert(node, copy);
// Copy parameters
Node::CopyInputs(node, copy, false);
}
void PreviewAutoCacher::UpdateLastSyncedValue()
{
last_update_time_ = QDateTime::currentMSecsSinceEpoch();
}
void PreviewAutoCacher::CancelQueuedSingleFrameRender()
{
if (single_frame_render_) {
// Signal that this ticket was cancelled with no value
single_frame_render_->Finish();
single_frame_render_ = nullptr;
}
}
void PreviewAutoCacher::SetPlayhead(const rational &playhead)
{
cache_range_ = TimeRange(playhead - Config::Current()[QStringLiteral("DiskCacheBehind")].value<rational>(),
playhead + Config::Current()[QStringLiteral("DiskCacheAhead")].value<rational>());
has_changed_ = true;
use_custom_range_ = false;
RequeueFrames();
}
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();
}
hash_tasks_.clear();
}
}
void PreviewAutoCacher::ClearVideoQueue(bool hard)
{
ClearQueueInternal(video_tasks_, hard, &PreviewAutoCacher::VideoRendered);
has_changed_ = true;
use_custom_range_ = false;
}
void PreviewAutoCacher::ClearAudioQueue(bool hard)
{
ClearQueueInternal(audio_tasks_, hard, &PreviewAutoCacher::AudioRendered);
}
void PreviewAutoCacher::ClearVideoDownloadQueue(bool hard)
{
ClearQueueInternal(video_download_tasks_, hard, &PreviewAutoCacher::VideoDownloaded);
}
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::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, RenderMode::kOffline, true));
}
}
invalidated_audio_.clear();
}
if (single_frame_render_) {
// Check if already caching this
QByteArray hash = single_frame_render_->property("hash").toByteArray();
RenderTicketWatcher* watcher;
if (!hash.isEmpty() && (watcher = video_tasks_.key(hash))) {
video_immediate_passthroughs_[watcher].append(single_frame_render_);
} else if (!hash.isEmpty() && (watcher = video_download_tasks_.key(hash))) {
single_frame_render_->Finish(watcher->property("frame"));
} else {
watcher = RenderFrame(hash,
single_frame_render_->property("time").value<rational>(),
single_frame_render_->property("prioritize").toBool(),
paused_);
video_immediate_passthroughs_[watcher].append(single_frame_render_);
}
single_frame_render_ = nullptr;
}
}
RenderTicketWatcher* PreviewAutoCacher::RenderFrame(const QByteArray &hash, const rational& time, bool prioritize, bool texture_only)
{
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_,
copied_color_manager_,
time,
RenderMode::kOffline,
viewer_node_->video_frame_cache(),
prioritize,
texture_only));
return watcher;
}
void PreviewAutoCacher::RequeueFrames()
{
delayed_requeue_timer_.stop();
if (viewer_node_
&& viewer_node_->video_frame_cache()->HasInvalidatedRanges()
&& hash_tasks_.isEmpty()
&& has_changed_
&& VideoParams::FormatIsFloat(viewer_node_->GetVideoParams().format())
&& (!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);
foreach (const rational& t, invalidated_ranges) {
const QByteArray& hash = viewer_node_->video_frame_cache()->GetHash(t);
RenderTicketWatcher* render_task = video_tasks_.key(hash);
if (t >= using_range.in()
&& t < using_range.out()) {
// We want this hash, if we're not already rendering, start render now
if (!render_task && !video_download_tasks_.key(hash)) {
// Don't render any hash more than once
RenderFrame(hash, t, false, false);
}
} else if (render_task) {
// Cancel this frame unless it's already started
QMutexLocker locker(render_task->GetTicket()->lock());
if (!render_task->GetTicket()->IsRunning(false)) {
video_tasks_.remove(render_task);
delete render_task;
}
}
}
has_changed_ = false;
}
}
void PreviewAutoCacher::ConformFinished()
{
last_conform_task_ = QDateTime::currentMSecsSinceEpoch();
if (viewer_node_) {
foreach (const TimeRange &range, audio_needing_conform_) {
viewer_node_->audio_playback_cache()->Invalidate(range, QDateTime::currentMSecsSinceEpoch());
}
audio_needing_conform_.clear();
}
}
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
// We need to wait for these since they send signals directly to the FrameHashCache
// which might get deleted after this function.
ClearHashQueue(true);
// This can be cleared normally (frames will be discarded and need to be rendered again)
ClearVideoQueue(true);
// This can be cleared normally (PCM data will be discarded and need to be rendered again)
ClearAudioQueue(true);
// 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);
// Clear any single frame render that might be queued
CancelQueuedSingleFrameRender();
// No more immediate passthroughts
video_immediate_passthroughs_.clear();
// No more audio conforms
audio_needing_conform_.clear();
// Delete all of our copied nodes
qDeleteAll(created_nodes_);
created_nodes_.clear();
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_->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
for (int i=0; i<copied_project_.nodes().size(); i++) {
InsertIntoCopyMap(graph->nodes().at(i), copied_project_.nodes().at(i));
}
for (int i=copied_project_.nodes().size(); i<graph->nodes().size(); i++) {
AddNode(graph->nodes().at(i));
}
// Find copied viewer node
copied_viewer_node_ = static_cast<ViewerOutput*>(copy_map_.value(viewer_node_));
copied_color_manager_ = static_cast<ColorManager*>(copy_map_.value(viewer_node_->project()->color_manager()));
// 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);
}
}
UpdateLastSyncedValue();
// 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();
connect(viewer_node_->video_frame_cache(),
&PlaybackCache::Invalidated,
this,
&PreviewAutoCacher::VideoInvalidated);
connect(viewer_node_->audio_playback_cache(),
&PlaybackCache::Invalidated,
this,
&PreviewAutoCacher::AudioInvalidated);
TryRender();
}
}
RenderTicketWatcher* RetrieveFromQueueIterator(QMap<RenderTicketWatcher*, QByteArray>::iterator it)
{
return it.key();
}
RenderTicketWatcher* RetrieveFromQueueIterator(QMap<RenderTicketWatcher*, TimeRange>::iterator it)
{
return it.key();
}
RenderTicketWatcher* RetrieveFromQueueIterator(QVector<RenderTicketWatcher*>::iterator it)
{
return *it;
}
void PreviewAutoCacher::ClearQueueRemoveEventInternal(QMap<RenderTicketWatcher*, QByteArray>::iterator it)
{
Q_UNUSED(it)
}
void PreviewAutoCacher::ClearQueueRemoveEventInternal(QMap<RenderTicketWatcher*, TimeRange>::iterator it)
{
Q_UNUSED(it)
}
void PreviewAutoCacher::ClearQueueRemoveEventInternal(QVector<RenderTicketWatcher*>::iterator it)
{
Q_UNUSED(it)
}
template<typename T, typename Func>
void PreviewAutoCacher::ClearQueueInternal(T& list, bool hard, Func member)
{
for (auto it=list.begin(); it!=list.end(); ) {
RenderTicketWatcher* ticket = RetrieveFromQueueIterator(it);
QMutexLocker locker(ticket->GetTicket()->lock());
bool ticket_is_running = ticket->GetTicket()->IsRunning(false);
if (hard || !ticket_is_running) {
// Override default signalling
disconnect(ticket, &RenderTicketWatcher::Finished, this, member);
if (ticket_is_running) {
// If ticket is running, assume we're hard clearing and wait for the ticket to finish
ticket->GetTicket()->WaitForFinished(ticket->GetTicket()->lock());
} else {
// Just remove the ticket from the queue
RenderManager::instance()->RemoveTicket(ticket->GetTicket());
}
// Special functionality for certain queues
ClearQueueRemoveEventInternal(it);
// Destroy ticket
locker.unlock();
delete ticket;
it = list.erase(it);
} else {
// We can't clear this, probably because we're soft clearing and the ticket is currently running
it++;
}
}
}
}