nodes: optimize node graph changes by only updating from inputs that changed
A huge optimization that ensures only the parts of a node graph that have changed get pushed to the renderer. For thread-safety, the node graph is copied elsewhere so that users can make changes asynchronously and the graph can update when its threads are ready. Up until now, if an input value changed, every node's values would be re-copied, or worse, if a connection was changed, the entire graph would be recopied. This has been negligible in testing since we've been largely testing with small graphs, but for massive projects, it's important that this be as optimized as possible.
This commit is contained in:
@@ -39,7 +39,7 @@ bool AudioBackend::InitInternal()
|
||||
// Initiate one thread per CPU core
|
||||
for (int i=0;i<threads().size();i++) {
|
||||
// Create one processor object for each thread
|
||||
AudioWorker* processor = new AudioWorker(©_map_);
|
||||
AudioWorker* processor = new AudioWorker(&node_copy_map_);
|
||||
processor->SetParameters(params());
|
||||
processors_.append(processor);
|
||||
}
|
||||
@@ -51,18 +51,6 @@ void AudioBackend::CloseInternal()
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioBackend::CompileInternal()
|
||||
{
|
||||
// This backend doesn't compile anything yet
|
||||
return AudioRenderBackend::CompileInternal();
|
||||
}
|
||||
|
||||
void AudioBackend::DecompileInternal()
|
||||
{
|
||||
// This backend doesn't compile anything yet
|
||||
AudioRenderBackend::DecompileInternal();
|
||||
}
|
||||
|
||||
void AudioBackend::ConnectWorkerToThis(RenderWorker *worker)
|
||||
{
|
||||
AudioRenderBackend::ConnectWorkerToThis(worker);
|
||||
|
||||
@@ -40,10 +40,6 @@ protected:
|
||||
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
virtual bool CompileInternal() override;
|
||||
|
||||
virtual void DecompileInternal() override;
|
||||
|
||||
virtual void ConnectWorkerToThis(RenderWorker* worker) override;
|
||||
|
||||
private slots:
|
||||
|
||||
@@ -58,31 +58,15 @@ void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms)
|
||||
void AudioRenderBackend::ConnectViewer(ViewerOutput *node)
|
||||
{
|
||||
connect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache);
|
||||
connect(node, &ViewerOutput::AudioGraphChanged, this, &AudioRenderBackend::QueueRecompile);
|
||||
connect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache);
|
||||
}
|
||||
|
||||
void AudioRenderBackend::DisconnectViewer(ViewerOutput *node)
|
||||
{
|
||||
disconnect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache);
|
||||
disconnect(node, &ViewerOutput::AudioGraphChanged, this, &AudioRenderBackend::QueueRecompile);
|
||||
disconnect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache);
|
||||
}
|
||||
|
||||
bool AudioRenderBackend::CompileInternal()
|
||||
{
|
||||
for (int i=0;i<copied_graph_.nodes().size();i++) {
|
||||
copy_map_.insert(copied_graph_.nodes().at(i), source_node_list_.at(i));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioRenderBackend::DecompileInternal()
|
||||
{
|
||||
copy_map_.clear();
|
||||
}
|
||||
|
||||
bool AudioRenderBackend::GenerateCacheIDInternal(QCryptographicHash &hash)
|
||||
{
|
||||
if (!params_.is_valid()) {
|
||||
@@ -187,7 +171,7 @@ void AudioRenderBackend::ConformUnavailable(StreamPtr stream, TimeRange range, r
|
||||
} else if (audio_stream->has_conformed_version(params)) {
|
||||
|
||||
// Index JUST finished, requeue this time
|
||||
InvalidateCache(range);
|
||||
InvalidateCache(range, nullptr);
|
||||
|
||||
} else {
|
||||
|
||||
@@ -215,7 +199,7 @@ void AudioRenderBackend::ConformUpdated(Stream *stream, AudioRenderingParams par
|
||||
|
||||
// Send invalidate cache signal
|
||||
ic_from_conform_ = true;
|
||||
InvalidateCache(copy.affected_range);
|
||||
InvalidateCache(copy.affected_range, nullptr);
|
||||
ic_from_conform_ = false;
|
||||
|
||||
}
|
||||
|
||||
@@ -54,10 +54,6 @@ protected:
|
||||
|
||||
virtual void DisconnectViewer(ViewerOutput* node) override;
|
||||
|
||||
virtual bool CompileInternal() override;
|
||||
|
||||
virtual void DecompileInternal() override;
|
||||
|
||||
/**
|
||||
* @brief Internal function for generating the cache ID
|
||||
*/
|
||||
@@ -73,8 +69,6 @@ protected:
|
||||
|
||||
virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range) override;
|
||||
|
||||
QHash<Node*, Node*> copy_map_;
|
||||
|
||||
private:
|
||||
struct ConformWaitInfo {
|
||||
StreamPtr stream;
|
||||
|
||||
@@ -291,14 +291,14 @@ void Exporter::EncoderOpenedSuccessfully()
|
||||
video_backend_->SetOperatingMode(VideoRenderWorker::kHashOnly);
|
||||
connect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
|
||||
|
||||
video_backend_->InvalidateCache(export_range_);
|
||||
video_backend_->InvalidateCache(export_range_, nullptr);
|
||||
}
|
||||
|
||||
if (!audio_done_) {
|
||||
// We set the audio backend to render the full sequence to the disk
|
||||
connect(audio_backend_, &AudioRenderBackend::AudioComplete, this, &Exporter::AudioRendered);
|
||||
|
||||
audio_backend_->InvalidateCache(export_range_);
|
||||
audio_backend_->InvalidateCache(export_range_, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ void Exporter::VideoHashesComplete()
|
||||
}
|
||||
|
||||
foreach (const TimeRange& range, ranges) {
|
||||
video_backend_->InvalidateCache(range);
|
||||
video_backend_->InvalidateCache(range, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,15 +89,6 @@ void OpenGLBackend::CloseInternal()
|
||||
VideoRenderBackend::CloseInternal();
|
||||
}
|
||||
|
||||
bool OpenGLBackend::CompileInternal()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLBackend::DecompileInternal()
|
||||
{
|
||||
}
|
||||
|
||||
void OpenGLBackend::ParamsChangedEvent()
|
||||
{
|
||||
// If we're initiated, we need to recreate the texture. Otherwise this backend isn't active so it doesn't matter.
|
||||
|
||||
@@ -43,10 +43,6 @@ protected:
|
||||
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
virtual bool CompileInternal() override;
|
||||
|
||||
virtual void DecompileInternal() override;
|
||||
|
||||
virtual void ParamsChangedEvent() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -31,12 +31,9 @@ OLIVE_NAMESPACE_ENTER
|
||||
|
||||
RenderBackend::RenderBackend(QObject *parent) :
|
||||
QObject(parent),
|
||||
compiled_(false),
|
||||
started_(false),
|
||||
viewer_node_(nullptr),
|
||||
copied_viewer_node_(nullptr),
|
||||
recompile_queued_(false),
|
||||
input_update_queued_(false)
|
||||
copied_viewer_node_(nullptr)
|
||||
{
|
||||
// FIXME: Don't create in CLI mode
|
||||
cancel_dialog_ = new RenderCancelDialog(Core::instance()->main_window());
|
||||
@@ -84,7 +81,7 @@ void RenderBackend::Close()
|
||||
|
||||
CancelQueue();
|
||||
|
||||
Decompile();
|
||||
SetViewerNode(nullptr);
|
||||
|
||||
CloseInternal();
|
||||
|
||||
@@ -114,23 +111,30 @@ const QString &RenderBackend::GetError() const
|
||||
|
||||
void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
|
||||
{
|
||||
if (viewer_node_ != nullptr) {
|
||||
if (viewer_node_) {
|
||||
CancelQueue();
|
||||
|
||||
DisconnectViewer(viewer_node_);
|
||||
|
||||
Decompile();
|
||||
copied_graph_.Clear();
|
||||
copied_viewer_node_ = nullptr;
|
||||
node_copy_map_.clear();
|
||||
}
|
||||
|
||||
viewer_node_ = viewer_node;
|
||||
|
||||
if (viewer_node_ != nullptr) {
|
||||
if (viewer_node_) {
|
||||
ConnectViewer(viewer_node_);
|
||||
|
||||
RegenerateCacheID();
|
||||
}
|
||||
|
||||
InvalidateCache(TimeRange(0, RATIONAL_MAX));
|
||||
copied_viewer_node_ = static_cast<ViewerOutput*>(viewer_node_->copy());
|
||||
copied_graph_.AddNode(copied_viewer_node_);
|
||||
node_copy_map_.insert(viewer_node_, copied_viewer_node_);
|
||||
|
||||
InvalidateCache(TimeRange(0, RATIONAL_MAX),
|
||||
static_cast<NodeInput*>(viewer_node_->GetInputWithID(GetDependentInput()->id())));
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderBackend::IsInitiated()
|
||||
@@ -138,58 +142,6 @@ bool RenderBackend::IsInitiated()
|
||||
return started_;
|
||||
}
|
||||
|
||||
bool RenderBackend::Compile()
|
||||
{
|
||||
if (compiled_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get dependencies of viewer node
|
||||
source_node_list_.append(viewer_node_);
|
||||
source_node_list_.append(viewer_node_->GetDependencies());
|
||||
|
||||
// Copy all dependencies into graph
|
||||
foreach (Node* n, source_node_list_) {
|
||||
Node* copy = n->copy();
|
||||
|
||||
Node::CopyInputs(n, copy, false);
|
||||
|
||||
copied_graph_.AddNode(copy);
|
||||
}
|
||||
|
||||
// We just copied the inputs, so if an input update is queued, it's unnecessary
|
||||
input_update_queued_ = false;
|
||||
|
||||
// We know that the first node will be the viewer node since we appended that first in the copy
|
||||
copied_viewer_node_ = static_cast<ViewerOutput*>(copied_graph_.nodes().first());
|
||||
|
||||
// Copy connections
|
||||
Node::DuplicateConnectionsBetweenLists(source_node_list_, copied_graph_.nodes());
|
||||
|
||||
compiled_ = CompileInternal();
|
||||
|
||||
if (!compiled_) {
|
||||
Decompile();
|
||||
}
|
||||
|
||||
return compiled_;
|
||||
}
|
||||
|
||||
void RenderBackend::Decompile()
|
||||
{
|
||||
if (!compiled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
DecompileInternal();
|
||||
|
||||
copied_graph_.Clear();
|
||||
copied_viewer_node_ = nullptr;
|
||||
source_node_list_.clear();
|
||||
|
||||
compiled_ = false;
|
||||
}
|
||||
|
||||
void RenderBackend::RegenerateCacheID()
|
||||
{
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
@@ -261,34 +213,19 @@ void RenderBackend::CacheNext()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Init()
|
||||
|| !ViewerIsConnected()
|
||||
|| !CanRender()) {
|
||||
if (!ViewerIsConnected()
|
||||
|| !CanRender()
|
||||
|| !Init()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((input_update_queued_ || recompile_queued_) && !AllProcessorsAreAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (recompile_queued_) {
|
||||
Decompile();
|
||||
recompile_queued_ = false;
|
||||
}
|
||||
|
||||
if (!compiled_ && !Compile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input_update_queued_) {
|
||||
for (int i=0;i<source_node_list_.size();i++) {
|
||||
Node* src = source_node_list_.at(i);
|
||||
Node* dst = copied_graph_.nodes().at(i);
|
||||
|
||||
Node::CopyInputs(src, dst, false);
|
||||
while (!input_update_queued_.isEmpty()) {
|
||||
if (!AllProcessorsAreAvailable()) {
|
||||
// To update the inputs, we need all workers to stop
|
||||
return;
|
||||
}
|
||||
|
||||
input_update_queued_ = false;
|
||||
CopyNodeInputValue(input_update_queued_.takeFirst());
|
||||
}
|
||||
|
||||
Node* node_connected_to_viewer = GetDependentInput()->get_connected_node();
|
||||
@@ -356,12 +293,8 @@ void RenderBackend::CancelQueue()
|
||||
cancel_dialog_->RunIfWorkersAreBusy();
|
||||
}
|
||||
|
||||
void RenderBackend::InvalidateCache(const TimeRange &range)
|
||||
void RenderBackend::InvalidateCache(const TimeRange &range, NodeInput *from)
|
||||
{
|
||||
if (!CanRender()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust range to min/max values
|
||||
rational start_range_adj = qMax(rational(0), range.in());
|
||||
rational end_range_adj = qMin(GetSequenceLength(), range.out());
|
||||
@@ -371,15 +304,18 @@ void RenderBackend::InvalidateCache(const TimeRange &range)
|
||||
<< "and"
|
||||
<< end_range_adj.toDouble();
|
||||
|
||||
// Queue value update
|
||||
QueueValueUpdate();
|
||||
if (from) {
|
||||
// Queue value update
|
||||
qDebug() << " from" << from->parentNode()->id() << "::" << from->id();
|
||||
QueueValueUpdate(from);
|
||||
}
|
||||
|
||||
InvalidateCacheInternal(start_range_adj, end_range_adj);
|
||||
}
|
||||
|
||||
bool RenderBackend::ViewerIsConnected() const
|
||||
{
|
||||
return viewer_node_ != nullptr;
|
||||
return viewer_node_;
|
||||
}
|
||||
|
||||
const QString &RenderBackend::cache_id() const
|
||||
@@ -387,9 +323,23 @@ const QString &RenderBackend::cache_id() const
|
||||
return cache_id_;
|
||||
}
|
||||
|
||||
void RenderBackend::QueueValueUpdate()
|
||||
void RenderBackend::QueueValueUpdate(NodeInput* from)
|
||||
{
|
||||
input_update_queued_ = true;
|
||||
if (!input_update_queued_.isEmpty()) {
|
||||
// Remove any inputs that are dependents of this input since they may have been removed since
|
||||
// it was queued
|
||||
QList<Node*> deps = from->GetDependencies();
|
||||
|
||||
for (int i=0;i<input_update_queued_.size();i++) {
|
||||
if (deps.contains(input_update_queued_.at(i)->parentNode())) {
|
||||
// We don't need to queue this value since this input supersedes it
|
||||
input_update_queued_.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input_update_queued_.append(from);
|
||||
}
|
||||
|
||||
bool RenderBackend::WorkerIsBusy(RenderWorker *worker) const
|
||||
@@ -402,6 +352,87 @@ void RenderBackend::SetWorkerBusyState(RenderWorker *worker, bool busy)
|
||||
processor_busy_state_.replace(processors_.indexOf(worker), busy);
|
||||
}
|
||||
|
||||
void RenderBackend::CopyNodeInputValue(NodeInput *input)
|
||||
{
|
||||
// Find our copy of this parameter
|
||||
Node* our_copy_node = node_copy_map_.value(input->parentNode());
|
||||
NodeInput* our_copy = our_copy_node->GetInputWithID(input->id());
|
||||
|
||||
// Copy the standard/keyframe values between these two inputs
|
||||
NodeInput::CopyValues(input,
|
||||
our_copy,
|
||||
false);
|
||||
|
||||
// Handle connections
|
||||
if (input->IsConnected() || our_copy->IsConnected()) {
|
||||
// If one of the inputs is connected, it's likely this change came from connecting or
|
||||
// disconnecting whatever was connected to it
|
||||
|
||||
{
|
||||
// We start by removing all old dependencies from the map
|
||||
QList<Node*> old_deps = our_copy->GetExclusiveDependencies();
|
||||
|
||||
foreach (Node* i, old_deps) {
|
||||
Node* n = node_copy_map_.take(node_copy_map_.key(i));
|
||||
copied_graph_.TakeNode(n);
|
||||
delete n;
|
||||
}
|
||||
}
|
||||
|
||||
// Then we copy all node dependencies and connections (if there are any)
|
||||
CopyNodeMakeConnection(input, our_copy);
|
||||
}
|
||||
|
||||
// Call on sub-elements too
|
||||
if (input->IsArray()) {
|
||||
foreach (NodeInput* i, static_cast<NodeInputArray*>(input)->sub_params()) {
|
||||
CopyNodeInputValue(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node* RenderBackend::CopyNodeConnections(Node* src_node)
|
||||
{
|
||||
// Check if this node is already in the map
|
||||
Node* dst_node = node_copy_map_.value(src_node);
|
||||
|
||||
// If not, create it now
|
||||
if (!dst_node) {
|
||||
dst_node = src_node->copy();
|
||||
copied_graph_.AddNode(dst_node);
|
||||
node_copy_map_.insert(src_node, dst_node);
|
||||
}
|
||||
|
||||
// Make sure its values are copied
|
||||
Node::CopyInputs(src_node, dst_node, false);
|
||||
|
||||
// Copy all connections
|
||||
QList<NodeInput*> src_node_inputs = src_node->GetInputsIncludingArrays();
|
||||
QList<NodeInput*> dst_node_inputs = dst_node->GetInputsIncludingArrays();
|
||||
|
||||
for (int i=0;i<src_node_inputs.size();i++) {
|
||||
NodeInput* src_input = src_node_inputs.at(i);
|
||||
|
||||
CopyNodeMakeConnection(src_input, dst_node_inputs.at(i));
|
||||
}
|
||||
|
||||
return dst_node;
|
||||
}
|
||||
|
||||
void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_input)
|
||||
{
|
||||
qDebug() << "Copying input" << src_input->id() << "from" << src_input->parentNode()->id();
|
||||
|
||||
if (src_input->IsConnected()) {
|
||||
Node* dst_node = CopyNodeConnections(src_input->get_connected_node());
|
||||
|
||||
NodeOutput* corresponding_output = dst_node->GetOutputWithID(src_input->get_connected_output()->id());
|
||||
|
||||
NodeParam::ConnectEdge(corresponding_output,
|
||||
dst_input);
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderBackend::AllProcessorsAreAvailable() const
|
||||
{
|
||||
foreach (bool busy, processor_busy_state_) {
|
||||
@@ -455,11 +486,6 @@ void RenderBackend::InitWorkers()
|
||||
processor_busy_state_.fill(false);
|
||||
}
|
||||
|
||||
void RenderBackend::QueueRecompile()
|
||||
{
|
||||
recompile_queued_ = true;
|
||||
}
|
||||
|
||||
void RenderBackend::FootageUnavailable(StreamPtr stream, Decoder::RetrieveState state, const TimeRange &range, const rational &stream_time)
|
||||
{
|
||||
if (state == Decoder::kFailedToOpen){
|
||||
@@ -484,7 +510,7 @@ void RenderBackend::FootageUnavailable(StreamPtr stream, Decoder::RetrieveState
|
||||
|| (stream->type() == Stream::kAudio && std::static_pointer_cast<AudioStream>(stream)->index_done())) {
|
||||
|
||||
// Index JUST finished, requeue this time
|
||||
InvalidateCache(range);
|
||||
InvalidateCache(range, nullptr);
|
||||
|
||||
} else {
|
||||
|
||||
@@ -529,7 +555,7 @@ void RenderBackend::IndexUpdated(Stream* stream)
|
||||
}
|
||||
|
||||
if (footage_ready) {
|
||||
InvalidateCache(info.affected_range);
|
||||
InvalidateCache(info.affected_range, nullptr);
|
||||
footage_wait_info_.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
@@ -52,11 +52,7 @@ public:
|
||||
void CancelQueue();
|
||||
|
||||
public slots:
|
||||
void InvalidateCache(const TimeRange &range);
|
||||
|
||||
bool Compile();
|
||||
|
||||
void Decompile();
|
||||
void InvalidateCache(const TimeRange &range, NodeInput *from);
|
||||
|
||||
signals:
|
||||
void QueueComplete();
|
||||
@@ -68,10 +64,6 @@ protected:
|
||||
|
||||
virtual void CloseInternal();
|
||||
|
||||
virtual bool CompileInternal() = 0;
|
||||
|
||||
virtual void DecompileInternal() = 0;
|
||||
|
||||
virtual bool CanRender();
|
||||
|
||||
virtual TimeRange PopNextFrameFromQueue();
|
||||
@@ -111,7 +103,7 @@ protected:
|
||||
|
||||
const QString& cache_id() const;
|
||||
|
||||
void QueueValueUpdate();
|
||||
void QueueValueUpdate(NodeInput *from);
|
||||
|
||||
bool AllProcessorsAreAvailable() const;
|
||||
bool WorkerIsBusy(RenderWorker* worker) const;
|
||||
@@ -121,18 +113,17 @@ protected:
|
||||
|
||||
QVector<RenderWorker*> processors_;
|
||||
|
||||
bool compiled_;
|
||||
|
||||
QHash<TimeRange, qint64> render_job_info_;
|
||||
|
||||
QList<Node*> source_node_list_;
|
||||
QHash<Node*, Node*> node_copy_map_;
|
||||
|
||||
NodeGraph copied_graph_;
|
||||
|
||||
protected slots:
|
||||
void QueueRecompile();
|
||||
|
||||
private:
|
||||
void CopyNodeInputValue(NodeInput* input);
|
||||
Node *CopyNodeConnections(Node *src_node);
|
||||
void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input);
|
||||
|
||||
/**
|
||||
* @brief Internal list of RenderProcessThreads
|
||||
*/
|
||||
@@ -160,8 +151,7 @@ private:
|
||||
|
||||
QString cache_id_;
|
||||
|
||||
bool recompile_queued_;
|
||||
bool input_update_queued_;
|
||||
QList<NodeInput*> input_update_queued_;
|
||||
|
||||
QVector<bool> processor_busy_state_;
|
||||
|
||||
|
||||
@@ -49,14 +49,12 @@ VideoRenderBackend::VideoRenderBackend(QObject *parent) :
|
||||
void VideoRenderBackend::ConnectViewer(ViewerOutput *node)
|
||||
{
|
||||
connect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache);
|
||||
connect(node, &ViewerOutput::VideoGraphChanged, this, &VideoRenderBackend::QueueRecompile);
|
||||
connect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength);
|
||||
}
|
||||
|
||||
void VideoRenderBackend::DisconnectViewer(ViewerOutput *node)
|
||||
{
|
||||
disconnect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache);
|
||||
disconnect(node, &ViewerOutput::VideoGraphChanged, this, &VideoRenderBackend::QueueRecompile);
|
||||
disconnect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength);
|
||||
|
||||
frame_cache_.Clear();
|
||||
|
||||
@@ -261,7 +261,10 @@ void VideoRenderWorker::SetFrameGenerationParams(int width, int height, const QM
|
||||
|
||||
bool VideoRenderWorker::InitInternal()
|
||||
{
|
||||
ResizeDownloadBuffer();
|
||||
if (video_params_.is_valid()) {
|
||||
ResizeDownloadBuffer();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user