/*** Olive - Non-Linear Video Editor Copyright (C) 2019 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 . ***/ #include "renderbackend.h" #include #include #include "config/config.h" #include "core.h" #include "task/conform/conform.h" #include "task/taskmanager.h" #include "window/mainwindow/mainwindow.h" OLIVE_NAMESPACE_ENTER RenderBackend::RenderBackend(QObject *parent) : QObject(parent), viewer_node_(nullptr), update_with_graph_(false), preview_job_time_(0), render_mode_(RenderMode::kOnline) { } RenderBackend::~RenderBackend() { Close(); } void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) { if (viewer_node_ == viewer_node) { return; } ViewerOutput* old_viewer = viewer_node_; if (!viewer_node) { // If setting to null, set it here before we wait for jobs to finish viewer_node_ = viewer_node; } if (old_viewer) { // Delete all of our copied nodes pool_.clear(); pool_.waitForDone(); // Cancel all tickets foreach (RenderTicketPtr t, render_queue_) { t->Cancel(); } render_queue_.clear(); // Delete all the nodes foreach (Node* c, copy_map_) { c->deleteLater(); } copy_map_.clear(); copied_viewer_node_ = nullptr; graph_update_queue_.clear(); disconnect(old_viewer, &ViewerOutput::GraphChangedFrom, this, &RenderBackend::NodeGraphChanged); } if (viewer_node) { // If setting to non-null, set it now viewer_node_ = viewer_node; } if (viewer_node_) { // Copy graph copied_viewer_node_ = static_cast(viewer_node_->copy()); copy_map_.insert(viewer_node_, copied_viewer_node_); NodeGraphChanged(viewer_node_->texture_input()); NodeGraphChanged(viewer_node_->samples_input()); ProcessUpdateQueue(); if (update_with_graph_) { connect(viewer_node_, &ViewerOutput::GraphChangedFrom, this, &RenderBackend::NodeGraphChanged); } } } void RenderBackend::ClearVideoQueue() { foreach (RenderTicketPtr t, render_queue_) { t->Cancel(); } render_queue_.clear(); } RenderTicketPtr RenderBackend::Hash(const QVector ×) { if (!viewer_node_) { return nullptr; } RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeHash, QVariant::fromValue(times)); render_queue_.push_back(ticket); QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection); return ticket; } RenderTicketPtr RenderBackend::RenderFrame(const rational &time) { if (!viewer_node_) { return nullptr; } RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeVideo, QVariant::fromValue(time)); render_queue_.push_back(ticket); QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection); return ticket; } RenderTicketPtr RenderBackend::RenderAudio(const TimeRange &r) { if (!viewer_node_) { return nullptr; } RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeAudio, QVariant::fromValue(r)); render_queue_.push_back(ticket); QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection); return ticket; } void RenderBackend::SetVideoParams(const VideoParams ¶ms) { video_params_ = params; } void RenderBackend::SetAudioParams(const AudioParams ¶ms) { audio_params_ = params; } void RenderBackend::SetVideoDownloadMatrix(const QMatrix4x4 &mat) { video_download_matrix_ = mat; } std::list RenderBackend::SplitRangeIntoChunks(const TimeRange &r) { // FIXME: Magic number const int chunk_size = 2; std::list split_ranges; int start_time = qFloor(r.in().toDouble() / static_cast(chunk_size)) * chunk_size; int end_time = qCeil(r.out().toDouble() / static_cast(chunk_size)) * chunk_size; for (int i=start_time; iparentNode()); // If we don't have this node yet, assume it's coming in a later copy in which case it'll be // copied then if (!our_copy_node) { // Assert that there are updates coming Q_ASSERT(!graph_update_queue_.isEmpty()); return; } // If we're here, we must have this node. Determine if we're already copying a "parent" of this for (int i=0; iparentNode()->OutputsTo(queued_input, true)) { // In which case, no further copy is necessary return; } // Check if this input supersedes an already queued input if (queued_input->parentNode()->OutputsTo(source, true)) { // In which case, we don't need to queue it and can queue our own graph_update_queue_.removeAt(i); i--; } } graph_update_queue_.append(source); } void RenderBackend::Close() { SetViewerNode(nullptr); for (int i=0;ideleteLater(); } workers_.clear(); } void RenderBackend::RunNextJob() { // If queue is empty, nothing to be done if (render_queue_.empty()) { return; } // Check if params are valid if (!video_params_.is_valid() || !audio_params_.is_valid()) { qDebug() << "Failed to run job, parameters are invalid"; return; } // If we have a value update queued, check if all workers are available and proceed from there if (update_with_graph_ && !graph_update_queue_.isEmpty()) { bool all_workers_available = true; foreach (const WorkerData& data, workers_) { if (data.busy) { all_workers_available = false; break; } } if (all_workers_available) { // Process queue ProcessUpdateQueue(); } else { return; } } // If we have no workers allocated, allocate them now if (workers_.isEmpty()) { // Allocate workers here workers_.resize(pool_.maxThreadCount()); for (int i=0;iSetVideoParams(video_params_); worker->SetAudioParams(audio_params_); worker->SetVideoDownloadMatrix(video_download_matrix_); worker->SetRenderMode(render_mode_); if (preview_job_time_) { worker->EnablePreviewGeneration(viewer_node_->audio_playback_cache(), preview_job_time_); } worker->SetCopyMap(©_map_); RenderTicketPtr ticket = render_queue_.front(); render_queue_.pop_front(); switch (ticket->GetType()) { case RenderTicket::kTypeHash: QtConcurrent::run(&pool_, worker, &RenderWorker::Hash, ticket, copied_viewer_node_, ticket->GetTime().value >()); break; case RenderTicket::kTypeVideo: QtConcurrent::run(&pool_, worker, &RenderWorker::RenderFrame, ticket, copied_viewer_node_, ticket->GetTime().value()); break; case RenderTicket::kTypeAudio: QtConcurrent::run(&pool_, worker, &RenderWorker::RenderAudio, ticket, copied_viewer_node_, ticket->GetTime().value()); break; } if (render_queue_.empty()) { // No more jobs, can exit here break; } } } } //#define PRINT_UPDATE_QUEUE_INFO void RenderBackend::ProcessUpdateQueue() { #ifdef PRINT_UPDATE_QUEUE_INFO qint64 t = QDateTime::currentMSecsSinceEpoch(); qDebug() << "Processing update queue of" << graph_update_queue_.size() << "elements:"; #endif while (!graph_update_queue_.isEmpty()) { NodeInput* i = graph_update_queue_.takeFirst(); #ifdef PRINT_UPDATE_QUEUE_INFO qDebug() << " " << i->parentNode()->id() << i->id(); #endif CopyNodeInputValue(i); } #ifdef PRINT_UPDATE_QUEUE_INFO qDebug() << "Update queue took:" << (QDateTime::currentMSecsSinceEpoch() - t); #endif } void RenderBackend::WorkerFinished() { RenderWorker* worker = static_cast(sender()); // Set busy state to false for (int i=0;iparentNode()); NodeInput* our_copy = our_copy_node->GetInputWithID(input->id()); // Copy the standard/keyframe values between these two inputs NodeInput::CopyValues(input, our_copy, false, false); // Handle connections if (input->is_connected() || our_copy->is_connected()) { // 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 old_deps = our_copy->GetExclusiveDependencies(); foreach (Node* i, old_deps) { copy_map_.take(copy_map_.key(i))->deleteLater(); } // And clear any other edges while (!our_copy->edges().isEmpty()) { NodeParam::DisconnectEdge(our_copy->edges().first()); } // 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(input)->sub_params()) { CopyNodeInputValue(i); } } } Node* RenderBackend::CopyNodeConnections(Node* src_node) { // Check if this node is already in the map Node* dst_node = copy_map_.value(src_node); // If not, create it now if (!dst_node) { dst_node = src_node->copy(); if (dst_node->IsTrack()) { // Hack that ensures the track type is set since we don't bother copying the whole timeline static_cast(dst_node)->set_track_type(static_cast(src_node)->track_type()); } copy_map_.insert(src_node, dst_node); } // Make sure its values are copied Node::CopyInputs(src_node, dst_node, false); // Copy all connections QList src_node_inputs = src_node->GetInputsIncludingArrays(); QList dst_node_inputs = dst_node->GetInputsIncludingArrays(); for (int i=0;iis_connected()) { 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); } } OLIVE_NAMESPACE_EXIT