Files
oak-editor/app/render/backend/renderbackend.cpp
T
itsmattkc cabf581a20 code: fixed typo
Not sure how this got here but ok
2020-07-10 18:45:45 +10:00

478 lines
13 KiB
C++

/***
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 <http://www.gnu.org/licenses/>.
***/
#include "renderbackend.h"
#include <QDateTime>
#include <QThread>
#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<ViewerOutput*>(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<rational> &times)
{
if (!viewer_node_) {
return nullptr;
}
RenderTicketPtr ticket = std::make_shared<RenderTicket>(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>(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>(RenderTicket::kTypeAudio,
QVariant::fromValue(r));
render_queue_.push_back(ticket);
QMetaObject::invokeMethod(this, "RunNextJob", Qt::QueuedConnection);
return ticket;
}
void RenderBackend::SetVideoParams(const VideoParams &params)
{
video_params_ = params;
}
void RenderBackend::SetAudioParams(const AudioParams &params)
{
audio_params_ = params;
}
void RenderBackend::SetVideoDownloadMatrix(const QMatrix4x4 &mat)
{
video_download_matrix_ = mat;
}
std::list<TimeRange> RenderBackend::SplitRangeIntoChunks(const TimeRange &r)
{
// FIXME: Magic number
const int chunk_size = 2;
std::list<TimeRange> split_ranges;
int start_time = qFloor(r.in().toDouble() / static_cast<double>(chunk_size)) * chunk_size;
int end_time = qCeil(r.out().toDouble() / static_cast<double>(chunk_size)) * chunk_size;
for (int i=start_time; i<end_time; i+=chunk_size) {
split_ranges.push_back(TimeRange(qMax(r.in(), rational(i)),
qMin(r.out(), rational(i + chunk_size))));
}
return split_ranges;
}
void RenderBackend::NodeGraphChanged(NodeInput *source)
{
// We need to determine:
// - If we don't have this input, assume that it's coming soon and ignore it
// - If we do, is this input a child of another input we're already copying?
// - Or are any of the queued inputs children of this one?
// First we need to find our copy of the input being queued
Node* our_copy_node = copy_map_.value(source->parentNode());
// 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; i<graph_update_queue_.size(); i++) {
NodeInput* queued_input = graph_update_queue_.at(i);
// If this input is already queued, nothing to be done
if (source == queued_input) {
return;
}
// Check if this dependency graph is already queued
if (source->parentNode()->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;i<workers_.size();i++) {
workers_.at(i).worker->deleteLater();
}
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;i<workers_.size();i++) {
RenderWorker* worker = CreateNewWorker();
connect(worker, &RenderWorker::FinishedJob, this, &RenderBackend::WorkerFinished);
workers_.replace(i, {worker, false});
}
}
// Start popping jobs off the queue
for (int i=0;i<workers_.size();i++) {
if (!workers_.at(i).busy) {
// This worker is available, send it the job
RenderWorker* worker = workers_[i].worker;
workers_[i].busy = true;
worker->SetVideoParams(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(&copy_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<QVector<rational> >());
break;
case RenderTicket::kTypeVideo:
QtConcurrent::run(&pool_,
worker,
&RenderWorker::RenderFrame,
ticket,
copied_viewer_node_,
ticket->GetTime().value<rational>());
break;
case RenderTicket::kTypeAudio:
QtConcurrent::run(&pool_,
worker,
&RenderWorker::RenderAudio,
ticket,
copied_viewer_node_,
ticket->GetTime().value<TimeRange>());
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<RenderWorker*>(sender());
// Set busy state to false
for (int i=0;i<workers_.size();i++) {
if (workers_.at(i).worker == worker) {
workers_[i].busy = false;
break;
}
}
if (viewer_node_) {
RunNextJob();
}
}
void RenderBackend::CopyNodeInputValue(NodeInput *input)
{
// Find our copy of this parameter
Node* our_copy_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,
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<Node*> 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<NodeInputArray*>(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<TrackOutput*>(dst_node)->set_track_type(static_cast<TrackOutput*>(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<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)
{
if (src_input->is_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