/*** Olive - Non-Linear Video Editor Copyright (C) 2022 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 "rendermanager.h" #include #include #include #include "config/config.h" #include "core.h" #include "render/opengl/openglrenderer.h" #include "renderprocessor.h" #include "task/conform/conform.h" #include "task/taskmanager.h" #include "window/mainwindow/mainwindow.h" namespace olive { RenderManager* RenderManager::instance_ = nullptr; const rational RenderManager::kDryRunInterval = rational(10); RenderManager::RenderManager(QObject *parent) : backend_(kOpenGL), aggressive_gc_(0) { if (backend_ == kOpenGL) { context_ = new OpenGLRenderer(); decoder_cache_ = new DecoderCache(); shader_cache_ = new ShaderCache(); } else { qCritical() << "Tried to initialize unknown graphics backend"; context_ = nullptr; decoder_cache_ = nullptr; } if (context_) { video_thread_ = new RenderThread(context_, decoder_cache_, shader_cache_, this); dry_run_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this); audio_thread_ = new RenderThread(nullptr, decoder_cache_, shader_cache_, this); video_thread_->start(QThread::IdlePriority); dry_run_thread_->start(QThread::IdlePriority); audio_thread_->start(QThread::IdlePriority); } decoder_clear_timer_ = new QTimer(this); decoder_clear_timer_->setInterval(kDecoderMaximumInactivity); connect(decoder_clear_timer_, &QTimer::timeout, this, &RenderManager::ClearOldDecoders); decoder_clear_timer_->start(); } RenderManager::~RenderManager() { if (context_) { delete shader_cache_; delete decoder_cache_; video_thread_->quit(); video_thread_->wait(); dry_run_thread_->quit(); dry_run_thread_->wait(); context_->PostDestroy(); delete context_; audio_thread_->quit(); audio_thread_->wait(); } } RenderTicketPtr RenderManager::RenderFrame(Node *node, const VideoParams &vparam, const AudioParams ¶m, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, FrameHashCache* cache, ReturnType return_type) { return RenderFrame(node, color_manager, time, mode, vparam, param, QSize(0, 0), QMatrix4x4(), VideoParams::kFormatInvalid, 0, nullptr, cache, return_type); } RenderTicketPtr RenderManager::RenderFrame(Node *node, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, const VideoParams &video_params, const AudioParams &audio_params, const QSize& force_size, const QMatrix4x4& force_matrix, VideoParams::Format force_format, int force_channel_count, ColorProcessorPtr force_color_output, FrameHashCache* cache, ReturnType return_type) { // Create ticket RenderTicketPtr ticket = std::make_shared(); ticket->setProperty("node", Node::PtrToValue(node)); ticket->setProperty("time", QVariant::fromValue(time)); ticket->setProperty("size", force_size); ticket->setProperty("matrix", force_matrix); ticket->setProperty("format", force_format); ticket->setProperty("channelcount", force_channel_count); ticket->setProperty("mode", mode); ticket->setProperty("type", kTypeVideo); ticket->setProperty("colormanager", Node::PtrToValue(color_manager)); ticket->setProperty("coloroutput", QVariant::fromValue(force_color_output)); ticket->setProperty("vparam", QVariant::fromValue(video_params)); ticket->setProperty("aparam", QVariant::fromValue(audio_params)); ticket->setProperty("return", return_type); if (cache) { ticket->setProperty("cache", cache->GetCacheDirectory()); ticket->setProperty("cachetimebase", QVariant::fromValue(cache->GetTimebase())); ticket->setProperty("cacheuuid", QVariant::fromValue(cache->GetUuid())); } if (return_type == ReturnType::kNull) { dry_run_thread_->AddTicket(ticket); } else { video_thread_->AddTicket(ticket); } return ticket; } RenderTicketPtr RenderManager::RenderAudio(Node *node, const TimeRange &r, const AudioParams ¶ms, RenderMode::Mode mode, bool generate_waveforms) { // Create ticket RenderTicketPtr ticket = std::make_shared(); ticket->setProperty("node", Node::PtrToValue(node)); ticket->setProperty("time", QVariant::fromValue(r)); ticket->setProperty("type", kTypeAudio); ticket->setProperty("mode", mode); ticket->setProperty("enablewaveforms", generate_waveforms); ticket->setProperty("aparam", QVariant::fromValue(params)); audio_thread_->AddTicket(ticket); return ticket; } bool RenderManager::RemoveTicket(RenderTicketPtr ticket) { if (video_thread_->RemoveTicket(ticket)) { return true; } else if (audio_thread_->RemoveTicket(ticket)) { return true; } else if (dry_run_thread_->RemoveTicket(ticket)) { return true; } else { return false; } } void RenderManager::SetAggressiveGarbageCollection(bool enabled) { aggressive_gc_ += enabled ? 1 : -1; if (aggressive_gc_ > 0) { decoder_clear_timer_->setInterval(kDecoderMaximumInactivityAggressive); } else { decoder_clear_timer_->setInterval(kDecoderMaximumInactivity); } } void RenderManager::ClearOldDecoders() { QMutexLocker locker(decoder_cache_->mutex()); qint64 min_age = QDateTime::currentMSecsSinceEpoch() - kDecoderMaximumInactivity; for (auto it=decoder_cache_->begin(); it!=decoder_cache_->end(); ) { DecoderPair decoder = it.value(); if (decoder.decoder->GetLastAccessedTime() < min_age) { decoder.decoder->Close(); it = decoder_cache_->erase(it); } else { it++; } } } RenderThread::RenderThread(Renderer *renderer, DecoderCache *decoder_cache, ShaderCache *shader_cache, QObject *parent) : QThread(parent), cancelled_(false), context_(renderer), decoder_cache_(decoder_cache), shader_cache_(shader_cache) { if (context_) { context_->Init(); context_->moveToThread(this); } } void RenderThread::AddTicket(RenderTicketPtr ticket) { QMutexLocker locker(&mutex_); queue_.push_back(ticket); wait_.wakeOne(); } bool RenderThread::RemoveTicket(RenderTicketPtr ticket) { QMutexLocker locker(&mutex_); auto it = std::find(queue_.begin(), queue_.end(), ticket); if (it == queue_.end()) { return false; } queue_.erase(it); return true; } void RenderThread::quit() { QMutexLocker locker(&mutex_); cancelled_ = true; wait_.wakeOne(); } void RenderThread::run() { if (context_) { context_->PostInit(); } QMutexLocker locker(&mutex_); while (!cancelled_) { if (queue_.empty()) { wait_.wait(&mutex_); } if (cancelled_) { break; } if (!queue_.empty()) { RenderTicketPtr ticket = queue_.front(); queue_.pop_front(); locker.unlock(); // Setup the ticket for ::Process ticket->Start(); if (ticket->IsCancelled()) { ticket->Finish(); } else { RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_); } locker.relock(); } } if (context_) { context_->Destroy(); context_->moveToThread(this->thread()); } } }