348 lines
10 KiB
C++
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();
|
|
}
|
|
|
|
}
|