Files
oak-editor/app/task/render/render.cpp
T

348 lines
10 KiB
C++

/***
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 <http://www.gnu.org/licenses/>.
***/
#include "render.h"
#include "node/project/sequence/sequence.h"
#include "render/rendermanager.h"
namespace olive
{
RenderTask::RenderTask()
: running_tickets_(0)
, native_progress_signalling_(true)
{
}
RenderTask::~RenderTask()
{
}
bool RenderTask::Render(ColorManager *manager, const TimeRangeList &video_range,
const TimeRangeList &audio_range,
const TimeRange &subtitle_range, RenderMode::Mode mode,
FrameHashCache *cache, const QSize &force_size,
const QMatrix4x4 &force_matrix,
PixelFormat force_format, int force_channel_count,
ColorProcessorPtr force_color_output)
{
QMetaObject::invokeMethod(RenderManager::instance(),
"SetAggressiveGarbageCollection",
Q_ARG(bool, true));
// Run watchers in another thread so they can accept signals even while this thread is blocked
QThread watcher_thread;
watcher_thread.start();
double progress_counter = 0;
double total_length = 0;
// Store real time before any rendering takes place
// Queue audio jobs
foreach (const TimeRange &range, audio_range) {
// Don't count audio progress, since it's generally a lot faster than video and is weighted at
// 50%, which makes the progress bar look weird to the uninitiated
//total_length += r.length().toDouble();
RenderManager::RenderAudioParams rap(
viewer_->GetConnectedSampleOutput(), range, audio_params_,
RenderMode::kOnline);
RenderTicketWatcher *watcher = new RenderTicketWatcher();
watcher->setProperty("range", QVariant::fromValue(range));
PrepareWatcher(watcher, &watcher_thread);
IncrementRunningTickets();
watcher->SetTicket(RenderManager::instance()->RenderAudio(rap));
}
// Look up hashes
TimeRangeListFrameIterator iterator(
video_range, video_params().frame_rate_as_time_base());
total_number_of_frames_ = iterator.size();
total_length += total_number_of_frames_;
// Start a render of a limited amount, and then render one frame for each frame that gets
// finished. This prevents rendered frames from stacking up in memory indefinitely while the
// encoder is processing them. The amount is kind of arbitrary, but we use the thread count so
// each of the system's threads are utilized as memory allows.
const int maximum_rendered_frames = QThread::idealThreadCount();
rational next_frame;
for (int i = 0;
i < maximum_rendered_frames && iterator.GetNext(&next_frame); i++) {
StartTicket(&watcher_thread, manager, next_frame, mode, cache,
force_size, force_matrix, force_format, force_channel_count,
force_color_output);
}
bool result = true;
// Subtitle loop, loops over all blocks in sequence on all tracks
if (!subtitle_range.length().isNull()) {
if (Sequence *sequence = dynamic_cast<Sequence *>(viewer_)) {
TrackList *list = sequence->track_list(Track::kSubtitle);
QVector<int> block_indexes(list->GetTrackCount(), 0);
QVector<int> tracks_to_push;
do {
tracks_to_push.clear();
for (int i = 0; i < block_indexes.size(); i++) {
Track *this_track = list->GetTrackAt(i);
if (this_track->IsMuted()) {
continue;
}
int &this_block_index = block_indexes[i];
if (this_block_index >= this_track->Blocks().size()) {
continue;
}
Block *this_block =
this_track->Blocks().at(this_block_index);
Track *compare_track =
tracks_to_push.isEmpty() ?
nullptr :
list->GetTrackAt(tracks_to_push.first());
const int &compare_block_index =
tracks_to_push.isEmpty() ?
-1 :
block_indexes.at(tracks_to_push.first());
Block *compare_block =
compare_track ?
compare_track->Blocks().at(compare_block_index) :
nullptr;
if (!compare_track ||
compare_block->in() >= this_block->in()) {
if (compare_track &&
compare_block->in() != this_block->in()) {
tracks_to_push.clear();
}
tracks_to_push.append(i);
}
}
for (int i = 0; i < tracks_to_push.size(); i++) {
Track *this_track = list->GetTrackAt(tracks_to_push.at(i));
Block *this_block = this_track->Blocks().at(
block_indexes.at(tracks_to_push.at(i)));
if (const SubtitleBlock *sub =
dynamic_cast<const SubtitleBlock *>(this_block)) {
if (sub->is_enabled()) {
if (!EncodeSubtitle(sub)) {
result = false;
break;
}
}
}
block_indexes[tracks_to_push.at(i)]++;
}
} while (!tracks_to_push.isEmpty());
}
}
finished_watcher_mutex_.lock();
while (result && !IsCancelled()) {
while (!finished_watchers_.empty() && !IsCancelled() && result) {
RenderTicketWatcher *watcher = finished_watchers_.front();
finished_watchers_.pop_front();
finished_watcher_mutex_.unlock();
// Analyze watcher here
RenderManager::TicketType ticket_type =
watcher->GetTicket()
->property("type")
.value<RenderManager::TicketType>();
if (ticket_type == RenderManager::kTypeAudio) {
TimeRange range = watcher->property("range").value<TimeRange>();
if (!AudioDownloaded(range,
watcher->Get().value<SampleBuffer>())) {
result = false;
}
// Don't count audio progress, since it's generally a lot faster than video and is weighted at
// 50%, which makes the progress bar look weird to the uninitiated
//progress_counter += range.length().toDouble();
//emit ProgressChanged(progress_counter / total_length);
} else if (ticket_type == RenderManager::kTypeVideo &&
TwoStepFrameRendering()) {
if (!DownloadFrame(
&watcher_thread, watcher->Get().value<FramePtr>(),
watcher->property("time").value<rational>())) {
result = false;
}
if (native_progress_signalling_) {
progress_counter += 0.5;
emit ProgressChanged(progress_counter / total_length);
}
} else {
// Assume single-step video or video download ticket
if (!FrameDownloaded(
watcher->Get().value<FramePtr>(),
watcher->property("time").value<rational>())) {
result = false;
}
if (native_progress_signalling_) {
double progress_to_add = 1.0;
if (TwoStepFrameRendering()) {
progress_to_add *= 0.5;
}
progress_counter += progress_to_add;
emit ProgressChanged(progress_counter / total_length);
}
if (iterator.GetNext(&next_frame)) {
StartTicket(&watcher_thread, manager, next_frame, mode,
cache, force_size, force_matrix, force_format,
force_channel_count, force_color_output);
}
}
delete watcher;
running_watchers_.removeOne(watcher);
finished_watcher_mutex_.lock();
}
if (IsCancelled() || !result) {
break;
}
// Run out of finished watchers. If we still have running tickets, wait for the next one to finish.
if (running_tickets_ > 0) {
finished_watcher_wait_cond_.wait(&finished_watcher_mutex_);
} else {
// No more running tickets or finished tickets, wem ust be
break;
}
}
finished_watcher_mutex_.unlock();
if (IsCancelled() || !result) {
// Cancel every watcher we created
foreach (RenderTicketWatcher *watcher, running_watchers_) {
watcher->Cancel();
disconnect(watcher, &RenderTicketWatcher::Finished, this,
&RenderTask::TicketDone);
RenderManager::instance()->RemoveTicket(watcher->GetTicket());
}
foreach (RenderTicketWatcher *watcher, running_watchers_) {
watcher->WaitForFinished();
}
}
watcher_thread.quit();
watcher_thread.wait();
QMetaObject::invokeMethod(RenderManager::instance(),
"SetAggressiveGarbageCollection",
Q_ARG(bool, false));
return result;
}
bool RenderTask::DownloadFrame(QThread *thread, FramePtr frame,
const rational &time)
{
//RenderTicketWatcher* watcher = new RenderTicketWatcher();
//PrepareWatcher(watcher, thread);
//IncrementRunningTickets();
//watcher->SetTicket(RenderManager::instance()->SaveFrameToCache(viewer_->video_frame_cache(), frame, time));
// NOTE: Doesn't reflect the actual return result of SaveFrameToCache
return true;
}
bool RenderTask::EncodeSubtitle(const SubtitleBlock *subtitle)
{
Q_UNUSED(subtitle)
return true;
}
void RenderTask::PrepareWatcher(RenderTicketWatcher *watcher, QThread *thread)
{
watcher->moveToThread(thread);
connect(watcher, &RenderTicketWatcher::Finished, this,
&RenderTask::TicketDone, Qt::DirectConnection);
running_watchers_.append(watcher);
}
void RenderTask::IncrementRunningTickets()
{
finished_watcher_mutex_.lock();
running_tickets_++;
finished_watcher_mutex_.unlock();
}
void RenderTask::StartTicket(QThread *watcher_thread, ColorManager *manager,
const rational &time, RenderMode::Mode mode,
FrameHashCache *cache, const QSize &force_size,
const QMatrix4x4 &force_matrix,
PixelFormat force_format, int force_channel_count,
ColorProcessorPtr force_color_output)
{
RenderManager::RenderVideoParams rvp(viewer_->GetConnectedTextureOutput(),
video_params_, audio_params_, time,
manager, mode);
rvp.force_size = force_size;
rvp.force_matrix = force_matrix;
rvp.force_format = force_format;
rvp.force_color_output = force_color_output;
rvp.force_channel_count = force_channel_count;
if (cache) {
rvp.AddCache(cache);
}
RenderTicketWatcher *watcher = new RenderTicketWatcher();
watcher->setProperty("time", QVariant::fromValue(time));
PrepareWatcher(watcher, watcher_thread);
IncrementRunningTickets();
watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp));
}
void RenderTask::TicketDone(RenderTicketWatcher *watcher)
{
finished_watcher_mutex_.lock();
finished_watchers_.push_back(watcher);
finished_watcher_wait_cond_.wakeAll();
running_tickets_--;
finished_watcher_mutex_.unlock();
}
}