Merge branch 'render-singleton'

This commit is contained in:
itsmattkc
2020-11-17 00:41:28 +11:00
268 changed files with 8039 additions and 8302 deletions
+7 -2
View File
@@ -33,7 +33,10 @@ ConformTask::ConformTask(AudioStreamPtr stream, const AudioParams& params) :
bool ConformTask::Run()
{
if (stream_->footage()->decoder().isEmpty()) {
// Conforming is done by the renderer now, but I would like to use something like this just to
// show progress
/*if (stream_->footage()->decoder().isEmpty()) {
SetError(tr("Failed to find decoder to conform audio stream"));
return false;
} else {
@@ -49,7 +52,9 @@ bool ConformTask::Run()
} else {
return true;
}
}
}*/
return true;
}
OLIVE_NAMESPACE_EXIT
+46 -54
View File
@@ -33,16 +33,22 @@ ExportTask::ExportTask(ViewerOutput* viewer_node,
params_(params)
{
SetTitle(tr("Exporting \"%1\"").arg(viewer_node->media_name()));
// Render highest quality
backend()->SetRenderMode(RenderMode::kOnline);
}
bool ExportTask::Run()
{
TimeRange range;
// For safety, if we're overwriting, we save to a temporary filename and then only overwrite it
// at the end
QString real_filename = params_.filename();
if (QFileInfo::exists(params_.filename())) {
// Generate a filename that definitely doesn't exist
params_.SetFilename(FileFunctions::GetSafeTemporaryFilename(real_filename));
}
encoder_ = Encoder::CreateFromID(params_.encoder(), params_);
if (!encoder_) {
SetError(tr("Failed to create encoder"));
return false;
@@ -64,20 +70,26 @@ bool ExportTask::Run()
frame_time_ = Timecode::time_to_timestamp(range.in(), viewer()->video_params().time_base());
QSize video_force_size;
QMatrix4x4 video_force_matrix;
if (params_.video_enabled()) {
// Ensure renderer always provides the same resolution
backend()->SetForceDownloadResolution(true);
// If a transformation matrix is applied to this video, create it here
if (params_.video_scaling_method() != ExportParams::kStretch) {
QMatrix4x4 mat = ExportParams::GenerateMatrix(params_.video_scaling_method(),
viewer()->video_params().width(),
viewer()->video_params().height(),
params_.video_params().width(),
params_.video_params().height());
if (viewer()->video_params().width() != params_.video_params().width()
|| params_.video_params().height() != params_.video_params().height()) {
video_force_size = QSize(params_.video_params().width(), params_.video_params().height());
backend()->SetVideoDownloadMatrix(mat);
if (params_.video_scaling_method() != ExportParams::kStretch) {
video_force_matrix = ExportParams::GenerateMatrix(params_.video_scaling_method(),
viewer()->video_params().width(),
viewer()->video_params().height(),
params_.video_params().width(),
params_.video_params().height());
}
} else {
// Disables forcing size in the renderer
video_force_size = QSize(0, 0);
}
// Create color processor
@@ -94,15 +106,17 @@ bool ExportTask::Run()
TimeRangeList video_range, audio_range;
if (params_.video_enabled()) {
video_range.append(range);
video_range = {range};
}
if (params_.audio_enabled()) {
audio_range.append(range);
audio_range = {range};
audio_data_.SetLength(range.length());
}
Render(video_range, audio_range, false);
Render(color_manager_, video_range, audio_range, RenderMode::kOnline, nullptr,
video_force_size, video_force_matrix, encoder_->GetDesiredPixelFormat(),
color_processor_);
bool success = true;
@@ -113,49 +127,28 @@ bool ExportTask::Run()
encoder_->Close();
encoder_->deleteLater();
delete encoder_;
// If cancelled, delete the file we made, which is always a file we created since we write to a
// temp file during the actual encoding process
if (IsCancelled()) {
QFile::remove(params_.filename());
} else if (params_.filename() != real_filename) {
// If we were writing to a temp file, overwrite now
if (!FileFunctions::RenameFileAllowOverwrite(params_.filename(), real_filename)) {
SetError(tr("Failed to overwrite \"%1\". Export has been saved as \"%2\" instead.")
.arg(real_filename, params_.filename()));
success = false;
}
}
return success;
}
void FrameColorConvert(ColorProcessorPtr processor, FramePtr frame)
{
// OCIO conversion requires a frame in 32F format
if (frame->format() != PixelFormat::PIX_FMT_RGBA32F
&& frame->format() != PixelFormat::PIX_FMT_RGB32F) {
PixelFormat::Format dst;
if (PixelFormat::FormatHasAlphaChannel(frame->format())) {
dst = PixelFormat::PIX_FMT_RGBA32F;
} else {
dst = PixelFormat::PIX_FMT_RGB32F;
}
frame = PixelFormat::ConvertPixelFormat(frame, dst);
}
// Color conversion must be done with unassociated alpha, and the pipeline is always associated
ColorManager::DisassociateAlpha(frame);
// Convert color space
processor->ConvertFrame(frame);
// Re-associate alpha
ColorManager::ReassociateAlpha(frame);
}
QFuture<void> ExportTask::DownloadFrame(FramePtr frame, const QByteArray &hash)
{
rendered_frame_.insert(hash, frame);
return QtConcurrent::run(FrameColorConvert, color_processor_, frame);
}
void ExportTask::FrameDownloaded(const QByteArray &hash, const std::list<rational> &times, qint64 job_time)
void ExportTask::FrameDownloaded(FramePtr f, const QByteArray &hash, const QVector<rational> &times, qint64 job_time)
{
Q_UNUSED(job_time)
FramePtr f = rendered_frame_.value(hash);
Q_UNUSED(hash)
foreach (const rational& t, times) {
time_map_.insert(t, f);
@@ -174,7 +167,6 @@ void ExportTask::FrameDownloaded(const QByteArray &hash, const std::list<rationa
encoder_->WriteFrame(time_map_.take(real_time), real_time);
frame_time_++;
}
}
+6 -5
View File
@@ -38,15 +38,16 @@ public:
protected:
virtual bool Run() override;
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) override;
virtual void FrameDownloaded(const QByteArray& hash, const std::list<rational>& times, qint64 job_time) override;
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times, qint64 job_time) override;
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override;
private:
QHash<QByteArray, FramePtr> rendered_frame_;
virtual bool TwoStepFrameRendering() const override
{
return false;
}
private:
QHash<rational, FramePtr> time_map_;
ColorManager* color_manager_;
+10 -15
View File
@@ -20,6 +20,8 @@
#include "precachetask.h"
#include "project/project.h"
OLIVE_NAMESPACE_ENTER
PreCacheTask::PreCacheTask(VideoStreamPtr footage, Sequence* sequence) :
@@ -29,9 +31,6 @@ PreCacheTask::PreCacheTask(VideoStreamPtr footage, Sequence* sequence) :
viewer()->set_video_params(sequence->video_params());
viewer()->set_audio_params(sequence->audio_params());
// Render fastest quality
backend()->SetRenderMode(RenderMode::kOffline);
video_node_ = new VideoInput();
video_node_->SetStream(footage);
@@ -39,13 +38,11 @@ PreCacheTask::PreCacheTask(VideoStreamPtr footage, Sequence* sequence) :
SetTitle(tr("Pre-caching %1:%2").arg(footage->footage()->filename(),
QString::number(footage->index())));
backend()->NodeGraphChanged(viewer()->texture_input());
backend()->ProcessUpdateQueue();
}
PreCacheTask::~PreCacheTask()
{
// We created this viewer node ourselves, so now we should delete it
delete viewer();
delete video_node_;
}
@@ -66,23 +63,21 @@ bool PreCacheTask::Run()
}
*/
Render(video_range, TimeRangeList(), true);
download_threads_.waitForDone();
Render(footage_->footage()->project()->color_manager(),
video_range,
TimeRangeList(),
RenderMode::kOnline,
viewer()->video_frame_cache());
return true;
}
QFuture<void> PreCacheTask::DownloadFrame(FramePtr frame, const QByteArray &hash)
{
return QtConcurrent::run(&download_threads_, viewer()->video_frame_cache(), &FrameHashCache::SaveCacheFrame, hash, frame);
}
void PreCacheTask::FrameDownloaded(const QByteArray &hash, const std::list<rational> &times, qint64 job_time)
void PreCacheTask::FrameDownloaded(FramePtr frame, const QByteArray &hash, const QVector<rational> &times, qint64 job_time)
{
// Do nothing. Pre-cache essentially just creates more frames in the cache, it doesn't need to do
// anything else.
Q_UNUSED(frame)
Q_UNUSED(hash)
Q_UNUSED(times)
Q_UNUSED(job_time)
+1 -5
View File
@@ -38,9 +38,7 @@ public:
protected:
virtual bool Run() override;
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) override;
virtual void FrameDownloaded(const QByteArray& hash, const std::list<rational>& times, qint64 job_time) override;
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times, qint64 job_time) override;
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override;
@@ -49,8 +47,6 @@ private:
VideoInput* video_node_;
QThreadPool download_threads_;
};
OLIVE_NAMESPACE_EXIT
+2 -2
View File
@@ -110,8 +110,8 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte
} else {
FootagePtr item = Decoder::ProbeMedia(model_->project(), file_info.absoluteFilePath(),
&IsCancelled());
FootagePtr item = Decoder::Probe(model_->project(), file_info.absoluteFilePath(),
&IsCancelled());
if (item) {
// See if this footage is an image sequence
+1 -1
View File
@@ -166,7 +166,7 @@ bool LoadOTIOTask::Run()
if (imported_footage.contains(footage_url)) {
probed_item = imported_footage.value(footage_url);
} else {
probed_item = Decoder::ProbeMedia(project_.get(), footage_url, &IsCancelled());
probed_item = Decoder::Probe(project_.get(), footage_url, &IsCancelled());
imported_footage.insert(footage_url, probed_item);
project_->root()->add_child(probed_item);
}
+5 -6
View File
@@ -38,7 +38,7 @@ ProjectSaveTask::ProjectSaveTask(ProjectPtr project) :
bool ProjectSaveTask::Run()
{
// File to temporarily save to (ensures we can't half-write the user's main file and crash)
QString temp_save = QDir(FileFunctions::GetTempFilePath()).filePath(QStringLiteral("tempsv"));
QString temp_save = FileFunctions::GetSafeTemporaryFilename(project_->filename());
QFile project_file(temp_save);
@@ -74,16 +74,15 @@ bool ProjectSaveTask::Run()
}
// Save was successful, we can now rewrite the original file
QFile original(project_->filename());
if ((!original.exists() || original.remove())
&& QFile::copy(temp_save, project_->filename())) {
if (FileFunctions::RenameFileAllowOverwrite(temp_save, project_->filename())) {
return true;
} else {
SetError(tr("Failed to write to \"%1\".").arg(project_->filename()));
SetError(tr("Failed to overwrite \"%1\". Project has been saved as \"%2\" instead.")
.arg(project_->filename(), temp_save));
return false;
}
} else {
SetError(tr("Failed to open file \"%1\" for writing.").arg(project_->filename()));
SetError(tr("Failed to open temporary file \"%1\" for writing.").arg(temp_save));
return false;
}
}
+1 -1
View File
@@ -133,7 +133,7 @@ opentimelineio::v1_0::Track *SaveOTIOTask::SerializeTrack(TrackOutput *track)
otio_clip->set_source_range(opentimelineio::v1_0::TimeRange(block->in().toRationalTime(),
block->length().toRationalTime()));
QList<MediaInput*> media_nodes = block->FindInputNodes<MediaInput>();
QVector<MediaInput*> media_nodes = block->FindInputNodes<MediaInput>();
if (!media_nodes.isEmpty()) {
auto media_ref = new opentimelineio::v1_0::ExternalReference(media_nodes.first()->stream()->footage()->filename().toStdString());
otio_clip->set_media_reference(media_ref);
+164 -166
View File
@@ -21,223 +21,221 @@
#include "render.h"
#include "common/timecodefunctions.h"
#include "render/rendermanager.h"
OLIVE_NAMESPACE_ENTER
RenderTask::RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams)
RenderTask::RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams) :
viewer_(viewer),
video_params_(vparams),
audio_params_(aparams),
running_tickets_(0)
{
backend_ = new OpenGLBackend();
backend_->SetViewerNode(viewer);
backend_->SetVideoParams(vparams);
backend_->SetAudioParams(aparams);
}
RenderTask::~RenderTask()
{
delete backend_;
}
struct TimeHashFuturePair {
rational time;
RenderTicketPtr hash_future;
};
struct HashTimePair {
rational time;
QByteArray hash;
};
struct HashFrameFuturePair {
QByteArray hash;
RenderTicketPtr frame_future;
};
struct RangeSampleFuturePair {
TimeRange range;
RenderTicketPtr sample_future;
};
struct HashDownloadFuturePair {
QByteArray hash;
QFuture<void> download_future;
qint64 job_time;
};
void RenderTask::Render(const TimeRangeList& video_range,
bool RenderTask::Render(ColorManager* manager,
const TimeRangeList& video_range,
const TimeRangeList &audio_range,
bool use_disk_cache)
RenderMode::Mode mode,
FrameHashCache* cache, const QSize &force_size,
const QMatrix4x4 &force_matrix, VideoParams::Format force_format,
ColorProcessorPtr force_color_output)
{
// 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;
double video_frame_sz = video_params().time_base().toDouble();
std::list<TimeRange> audio_queue;
std::list<RangeSampleFuturePair> audio_lookup_table;
if (!audio_range.isEmpty()) {
foreach (const TimeRange& r, audio_range) {
total_length += r.length().toDouble();
// Store real time before any rendering takes place
qint64 job_time = QDateTime::currentMSecsSinceEpoch();
std::list<TimeRange> ranges = RenderBackend::SplitRangeIntoChunks(r);
audio_queue.insert(audio_queue.end(), ranges.begin(), ranges.end());
}
// Queue audio jobs
foreach (const TimeRange& r, 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();
IncrementRunningTickets();
RenderTicketWatcher* watcher = CreateWatcher(&watcher_thread);
watcher->setProperty("range", QVariant::fromValue(r));
watcher->SetTicket(RenderManager::instance()->RenderAudio(viewer_, r, audio_params_, false));
}
std::list<HashFrameFuturePair> render_lookup_table;
QVector<rational> times;
QVector<QByteArray> hashes;
std::list<HashTimePair> frame_queue;
qint64 hash_job_time = 0;
// Look up hashes
QMap<QByteArray, QVector<rational> > time_map;
if (!video_range.isEmpty()) {
times = viewer()->video_frame_cache()->GetFrameListFromTimeRange(video_range);
// Get list of discrete frames from range
QVector<rational> times = viewer()->video_frame_cache()->GetFrameListFromTimeRange(video_range);
QVector<QByteArray> hashes(times.size());
// Add to "total progress"
total_length += video_frame_sz * times.size();
RenderTicketPtr hash_future = backend_->Hash(times);
hashes = hash_future->Get().value<QVector<QByteArray> >();
hash_job_time = hash_future->GetJobTime();
// Generate hashes
for (int i=0; i<times.size(); i++) {
if (IsCancelled()) {
return true;
}
if (!hash_future->WasCancelled()) {
for (int i=0;i<times.size();i++) {
frame_queue.push_back({times.at(i), hashes.at(i)});
hashes[i] = RenderManager::instance()->Hash(viewer(), video_params_, times.at(i));
}
// Filter out duplicates
for (int i=0; i<hashes.size(); i++) {
if (IsCancelled()) {
return true;
}
const QByteArray& hash = hashes.at(i);
time_map[hash].append(times.at(i));
if (time_map[hash].size() == 1) {
// This is the first frame with this hash, so we signal a render
RenderTicketWatcher* watcher = CreateWatcher(&watcher_thread);
watcher->setProperty("hash", hash);
IncrementRunningTickets();
watcher->SetTicket(RenderManager::instance()->RenderFrame(viewer_, manager, times.at(i),
mode, video_params_, audio_params_,
force_size, force_matrix,
force_format, force_color_output,
cache));
}
}
}
// Start downloading frames that have finished
std::list<HashDownloadFuturePair> download_futures;
finished_watcher_mutex_.lock();
// Iterators
std::list<HashFrameFuturePair>::iterator i;
std::list<HashDownloadFuturePair>::iterator j;
std::list<RangeSampleFuturePair>::iterator k;
while (!IsCancelled()) {
while (!finished_watchers_.empty() && !IsCancelled()) {
RenderTicketWatcher* watcher = finished_watchers_.front();
finished_watchers_.pop_front();
std::list<QByteArray> running_hashes;
std::list<QByteArray> existing_hashes;
finished_watcher_mutex_.unlock();
while (!IsCancelled()
&& (!render_lookup_table.empty()
|| !frame_queue.empty()
|| !audio_queue.empty()
|| !download_futures.empty()
|| !audio_lookup_table.empty())) {
// Analyze watcher here
RenderManager::TicketType ticket_type = watcher->GetTicket()->property("type").value<RenderManager::TicketType>();
while (!IsCancelled() && !frame_queue.empty()) {
if (ticket_type == RenderManager::kTypeAudio) {
// Pop another frame off the frame queue
const HashTimePair& p = frame_queue.front();
TimeRange range = watcher->property("range").value<TimeRange>();
// Check if we're already rendering this hash
bool rendering_hash = (std::find(running_hashes.begin(), running_hashes.end(), p.hash) != running_hashes.end());
AudioDownloaded(range,
watcher->Get().value<SampleBufferPtr>(),
job_time);
// Skip this hash if we're already rendering it
if (!rendering_hash) {
// Check if this frame already exists (has already been rendered previously or during this job)
bool hash_exists = 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);
if (use_disk_cache) {
// Check if this hash is in our "existing hashes" list
hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), p.hash) != existing_hashes.end());
} else if (ticket_type == RenderManager::kTypeVideo && TwoStepFrameRendering()) {
// If not, check if it's in the filesystem
if (!hash_exists) {
hash_exists = QFileInfo::exists(viewer()->video_frame_cache()->CachePathName(p.hash));
DownloadFrame(&watcher_thread,
watcher->Get().value<FramePtr>(),
watcher->property("hash").toByteArray());
// If so, add it to the list so we don't have to check the filesystem again later
if (hash_exists) {
existing_hashes.push_back(p.hash);
}
}
if (hash_exists) {
// Already exists, no need to render it again
FrameDownloaded(p.hash, {p.time}, hash_job_time);
progress_counter += video_frame_sz;
emit ProgressChanged(progress_counter / total_length);
}
}
// If no existing disk cache was found, queue it now
if (!hash_exists) {
render_lookup_table.push_back({p.hash, backend_->RenderFrame(p.time)});
running_hashes.push_back(p.hash);
}
}
// Remove first element
frame_queue.pop_front();
}
while (!IsCancelled() && !audio_queue.empty()) {
audio_lookup_table.push_back({audio_queue.front(), backend_->RenderAudio(audio_queue.front())});
audio_queue.pop_front();
}
i = render_lookup_table.begin();
while (!IsCancelled() && i != render_lookup_table.end()) {
if (i->frame_future->IsFinished()) {
if (!i->frame_future->WasCancelled()) {
FramePtr f = i->frame_future->Get().value<FramePtr>();
// Start multithreaded download here
download_futures.push_back({i->hash, DownloadFrame(f, i->hash), i->frame_future->GetJobTime()});
}
i = render_lookup_table.erase(i);
} else {
i++;
}
}
j = download_futures.begin();
while (!IsCancelled() && j != download_futures.end()) {
if (j->download_future.isFinished()) {
// Place it in the cache
std::list<rational> times_with_hash;
for (int hash_index=0;hash_index<hashes.size();hash_index++) {
if (hashes.at(hash_index) == j->hash) {
times_with_hash.push_back(times.at(hash_index));
}
}
FrameDownloaded(j->hash, times_with_hash, j->job_time);
existing_hashes.push_back(j->hash);
// Signal process
progress_counter += times_with_hash.size() * video_frame_sz;
progress_counter += video_frame_sz * 0.5;
emit ProgressChanged(progress_counter / total_length);
j = download_futures.erase(j);
} else {
j++;
}
}
k = audio_lookup_table.begin();
// Assume single-step video or video download ticket
QByteArray rendered_hash = watcher->property("hash").toByteArray();
FrameDownloaded(watcher->Get().value<FramePtr>(), rendered_hash, time_map.value(rendered_hash), job_time);
while (!IsCancelled() && k != audio_lookup_table.end()) {
if (k->sample_future->IsFinished()) {
AudioDownloaded(k->range,
k->sample_future->Get().value<SampleBufferPtr>(),
k->sample_future->GetJobTime());
double progress_to_add = video_frame_sz;
if (TwoStepFrameRendering()) {
progress_to_add *= 0.5;
}
progress_counter += progress_to_add;
progress_counter += k->range.length().toDouble();
emit ProgressChanged(progress_counter / total_length);
k = audio_lookup_table.erase(k);
} else {
k++;
}
delete watcher;
running_watchers_.removeOne(watcher);
finished_watcher_mutex_.lock();
}
if (IsCancelled()) {
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;
}
}
// `Close` will block until all jobs are done making a safe deletion
backend_->Close();
finished_watcher_mutex_.unlock();
if (IsCancelled()) {
// Cancel every watcher we created
foreach (RenderTicketWatcher* watcher, running_watchers_) {
disconnect(watcher, &RenderTicketWatcher::Finished, this, &RenderTask::TicketDone);
watcher->Cancel();
}
}
watcher_thread.quit();
watcher_thread.wait();
return true;
}
void RenderTask::DownloadFrame(QThread *thread, FramePtr frame, const QByteArray &hash)
{
RenderTicketWatcher* watcher = CreateWatcher(thread);
watcher->setProperty("hash", hash);
IncrementRunningTickets();
watcher->SetTicket(RenderManager::instance()->SaveFrameToCache(viewer_->video_frame_cache(),
frame,
hash));
}
RenderTicketWatcher *RenderTask::CreateWatcher(QThread *thread)
{
RenderTicketWatcher* watcher = new RenderTicketWatcher();
watcher->moveToThread(thread);
connect(watcher, &RenderTicketWatcher::Finished, this, &RenderTask::TicketDone, Qt::DirectConnection);
running_watchers_.append(watcher);
return watcher;
}
void RenderTask::IncrementRunningTickets()
{
finished_watcher_mutex_.lock();
running_tickets_++;
finished_watcher_mutex_.unlock();
}
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();
}
OLIVE_NAMESPACE_EXIT
+44 -14
View File
@@ -24,51 +24,81 @@
#include <QtConcurrent/QtConcurrent>
#include "node/output/viewer/viewer.h"
#include "render/backend/opengl/openglbackend.h"
#include "render/colormanager.h"
#include "task/task.h"
#include "threading/threadticket.h"
#include "threading/threadticketwatcher.h"
OLIVE_NAMESPACE_ENTER
class RenderTask : public Task
{
Q_OBJECT
public:
RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams);
virtual ~RenderTask() override;
protected:
void Render(const TimeRangeList &video_range,
const TimeRangeList &audio_range,
bool use_disk_cache);
bool Render(ColorManager *manager, const TimeRangeList &video_range,
const TimeRangeList &audio_range, RenderMode::Mode mode,
FrameHashCache *cache, const QSize& force_size = QSize(0, 0),
const QMatrix4x4& force_matrix = QMatrix4x4(),
VideoParams::Format force_format = VideoParams::kFormatInvalid,
ColorProcessorPtr force_color_output = nullptr);
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) = 0;
virtual void DownloadFrame(QThread* thread, FramePtr frame, const QByteArray &hash);
virtual void FrameDownloaded(const QByteArray& hash, const std::list<rational>& times, qint64 job_time) = 0;
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times, qint64 job_time) = 0;
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) = 0;
ViewerOutput* viewer() const
{
return backend_->GetViewerNode();
return viewer_;
}
VideoParams video_params() const
const VideoParams& video_params() const
{
return backend_->GetVideoParams();
return video_params_;
}
AudioParams audio_params() const
const AudioParams& audio_params() const
{
return backend_->GetAudioParams();
return audio_params_;
}
RenderBackend* backend()
virtual void CancelEvent() override
{
return backend_;
finished_watcher_mutex_.lock();
finished_watcher_wait_cond_.wakeAll();
finished_watcher_mutex_.unlock();
}
virtual bool TwoStepFrameRendering() const
{
return true;
}
private:
RenderBackend* backend_;
RenderTicketWatcher* CreateWatcher(QThread *thread);
void IncrementRunningTickets();
ViewerOutput* viewer_;
VideoParams video_params_;
AudioParams audio_params_;
QVector<RenderTicketWatcher*> running_watchers_;
std::list<RenderTicketWatcher*> finished_watchers_;
int running_tickets_;
QMutex finished_watcher_mutex_;
QWaitCondition finished_watcher_wait_cond_;
private slots:
void TicketDone(RenderTicketWatcher *watcher);
};