renderer: optimizing and fixing code paths
Re-introducing some old concepts for the new structure.
This commit is contained in:
@@ -54,6 +54,7 @@ find_package(Qt5 5.6 REQUIRED
|
||||
OpenGL
|
||||
Svg
|
||||
LinguistTools
|
||||
Concurrent
|
||||
)
|
||||
|
||||
find_package(FFMPEG 3.0 REQUIRED
|
||||
|
||||
@@ -138,6 +138,7 @@ target_link_libraries(
|
||||
Qt5::Multimedia
|
||||
Qt5::OpenGL
|
||||
Qt5::Svg
|
||||
Qt5::Concurrent
|
||||
OpenGL::GL
|
||||
FFMPEG::avutil
|
||||
FFMPEG::avcodec
|
||||
|
||||
@@ -83,7 +83,7 @@ void Config::SetDefaults()
|
||||
config_map_["DropWithoutSequenceBehavior"] = TimelineWidget::kDWSAsk;
|
||||
config_map_["Loop"] = false;
|
||||
|
||||
config_map_["AutoCache"] = true;
|
||||
config_map_["AutoCache"] = true;//false;//true;
|
||||
config_map_["AutoCacheInterval"] = 1000;
|
||||
|
||||
config_map_["NodeCatColor0"] = QVariant::fromValue(Color(0.75f, 0.75f, 0.75f));
|
||||
|
||||
@@ -18,15 +18,15 @@ add_subdirectory(opengl)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
|
||||
render/backend/renderbackend.h
|
||||
render/backend/renderbackend.cpp
|
||||
|
||||
render/backend/renderworker.h
|
||||
render/backend/renderworker.cpp
|
||||
|
||||
render/backend/colorprocessorcache.h
|
||||
render/backend/decodercache.h
|
||||
|
||||
render/backend/renderbackend.h
|
||||
render/backend/renderbackend.cpp
|
||||
render/backend/renderticket.h
|
||||
render/backend/renderticket.cpp
|
||||
render/backend/renderticketwatcher.h
|
||||
render/backend/renderticketwatcher.cpp
|
||||
render/backend/renderworker.h
|
||||
render/backend/renderworker.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -34,11 +34,8 @@ OLIVE_NAMESPACE_ENTER
|
||||
RenderBackend::RenderBackend(QObject *parent) :
|
||||
QObject(parent),
|
||||
viewer_node_(nullptr),
|
||||
auto_audio_(true),
|
||||
ic_from_conform_(false)
|
||||
update_with_graph_(false)
|
||||
{
|
||||
// FIXME: Don't create in CLI mode
|
||||
//cancel_dialog_ = new RenderCancelDialog(Core::instance()->main_window());
|
||||
}
|
||||
|
||||
RenderBackend::~RenderBackend()
|
||||
@@ -53,92 +50,112 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
|
||||
}
|
||||
|
||||
if (viewer_node_) {
|
||||
// Clear queue and wait for any currently running actions to complete
|
||||
CancelQueue();
|
||||
|
||||
// Delete all of our copied nodes
|
||||
hash_pool_.Close();
|
||||
video_pool_.Close();
|
||||
audio_pool_.Close();
|
||||
queued_audio_.clear();
|
||||
pool_.clear();
|
||||
pool_.waitForDone();
|
||||
|
||||
// Cancel all tickets
|
||||
foreach (RenderTicketPtr t, render_queue_) {
|
||||
t->Cancel();
|
||||
}
|
||||
render_queue_.clear();
|
||||
|
||||
// Delete all the nodes
|
||||
qDeleteAll(copy_map_);
|
||||
copy_map_.clear();
|
||||
copied_viewer_node_ = nullptr;
|
||||
|
||||
disconnect(viewer_node_,
|
||||
&ViewerOutput::GraphChangedFrom,
|
||||
this,
|
||||
&RenderBackend::NodeGraphChanged);
|
||||
|
||||
disconnect(viewer_node_->audio_playback_cache(),
|
||||
&AudioPlaybackCache::Invalidated,
|
||||
this,
|
||||
&RenderBackend::AudioInvalidated);
|
||||
}
|
||||
|
||||
// Set viewer node
|
||||
viewer_node_ = viewer_node;
|
||||
|
||||
if (viewer_node_) {
|
||||
// Initiate instances with new node
|
||||
hash_pool_.Init(viewer_node_);
|
||||
video_pool_.Init(viewer_node_);
|
||||
audio_pool_.Init(viewer_node_);
|
||||
// Copy graph
|
||||
copied_viewer_node_ = static_cast<ViewerOutput*>(viewer_node_->copy());
|
||||
copy_map_.insert(viewer_node_, copied_viewer_node_);
|
||||
|
||||
connect(viewer_node_,
|
||||
&ViewerOutput::GraphChangedFrom,
|
||||
this,
|
||||
&RenderBackend::NodeGraphChanged);
|
||||
NodeGraphChanged(viewer_node_->texture_input());
|
||||
NodeGraphChanged(viewer_node_->samples_input());
|
||||
ProcessUpdateQueue();
|
||||
|
||||
if (auto_audio_) {
|
||||
// Listen for audio invalidation signals
|
||||
connect(viewer_node_->audio_playback_cache(),
|
||||
&AudioPlaybackCache::Invalidated,
|
||||
if (update_with_graph_) {
|
||||
connect(viewer_node_,
|
||||
&ViewerOutput::GraphChangedFrom,
|
||||
this,
|
||||
&RenderBackend::AudioInvalidated);
|
||||
|
||||
// Start caching audio
|
||||
foreach (const TimeRange& r, viewer_node_->audio_playback_cache()->GetInvalidatedRanges()) {
|
||||
AudioInvalidated(r);
|
||||
}
|
||||
&RenderBackend::NodeGraphChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::CancelQueue()
|
||||
void RenderBackend::SetUpdateWithGraph(bool e)
|
||||
{
|
||||
// FIXME: Implement something better than this...
|
||||
video_pool_.threads.waitForDone();
|
||||
audio_pool_.threads.waitForDone();
|
||||
hash_pool_.threads.waitForDone();
|
||||
update_with_graph_ = e;
|
||||
}
|
||||
|
||||
QFuture<QByteArray> RenderBackend::Hash(const rational &time, bool block_for_update)
|
||||
void RenderBackend::ClearVideoQueue()
|
||||
{
|
||||
return QtConcurrent::run(&hash_pool_.threads,
|
||||
GetInstanceFromPool(hash_pool_),
|
||||
&RenderWorker::Hash,
|
||||
time,
|
||||
block_for_update);
|
||||
foreach (RenderTicketPtr t, render_queue_) {
|
||||
t->Cancel();
|
||||
}
|
||||
render_queue_.clear();
|
||||
}
|
||||
|
||||
QFuture<FramePtr> RenderBackend::RenderFrame(const rational &time, bool clear_queue, bool block_for_update)
|
||||
QFuture<QList<QByteArray> > RenderBackend::Hash(const QList<rational> ×)
|
||||
{
|
||||
if (clear_queue) {
|
||||
video_pool_.threads.clear();
|
||||
return QtConcurrent::run([this](const QList<rational> ×){
|
||||
QList<QByteArray> hashes;
|
||||
|
||||
foreach (const rational& t, times) {
|
||||
QCryptographicHash hasher(QCryptographicHash::Sha1);
|
||||
|
||||
// Embed video parameters into this hash
|
||||
hasher.addData(reinterpret_cast<const char*>(&video_params_.effective_width()), sizeof(int));
|
||||
hasher.addData(reinterpret_cast<const char*>(&video_params_.effective_height()), sizeof(int));
|
||||
hasher.addData(reinterpret_cast<const char*>(&video_params_.format()), sizeof(PixelFormat::Format));
|
||||
hasher.addData(reinterpret_cast<const char*>(&video_params_.mode()), sizeof(RenderMode::Mode));
|
||||
|
||||
copied_viewer_node_->Hash(hasher, t);
|
||||
|
||||
hashes.append(hasher.result());
|
||||
}
|
||||
|
||||
return hashes;
|
||||
}, times);
|
||||
}
|
||||
|
||||
RenderTicketPtr RenderBackend::RenderFrame(const rational &time)
|
||||
{
|
||||
if (!viewer_node_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return QtConcurrent::run(&video_pool_.threads,
|
||||
GetInstanceFromPool(video_pool_),
|
||||
&RenderWorker::RenderFrame,
|
||||
time,
|
||||
block_for_update);
|
||||
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeVideo,
|
||||
TimeRange(time, time));
|
||||
|
||||
render_queue_.append(ticket);
|
||||
|
||||
RunNextJob();
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
QFuture<SampleBufferPtr> RenderBackend::RenderAudio(const TimeRange &r, bool block_for_update)
|
||||
RenderTicketPtr RenderBackend::RenderAudio(const TimeRange &r)
|
||||
{
|
||||
return QtConcurrent::run(&audio_pool_.threads,
|
||||
GetInstanceFromPool(audio_pool_),
|
||||
&RenderWorker::RenderAudio,
|
||||
r,
|
||||
block_for_update);
|
||||
if (!viewer_node_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeAudio,
|
||||
r);
|
||||
|
||||
render_queue_.append(ticket);
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
void RenderBackend::SetVideoParams(const VideoRenderingParams ¶ms)
|
||||
@@ -156,18 +173,6 @@ void RenderBackend::SetVideoDownloadMatrix(const QMatrix4x4 &mat)
|
||||
video_download_matrix_ = mat;
|
||||
}
|
||||
|
||||
void RenderBackend::SetAutomaticAudio(bool e)
|
||||
{
|
||||
auto_audio_ = e;
|
||||
}
|
||||
|
||||
void RenderBackend::WorkerStartedRenderingAudio(const TimeRange &r)
|
||||
{
|
||||
queued_audio_lock_.lock();
|
||||
queued_audio_.RemoveTimeRange(r);
|
||||
queued_audio_lock_.unlock();
|
||||
}
|
||||
|
||||
QList<TimeRange> RenderBackend::SplitRangeIntoChunks(const TimeRange &r)
|
||||
{
|
||||
const int chunk_size = 2;
|
||||
@@ -179,7 +184,7 @@ QList<TimeRange> RenderBackend::SplitRangeIntoChunks(const TimeRange &r)
|
||||
|
||||
for (int i=start_time; i<end_time; i+=chunk_size) {
|
||||
split_ranges.append(TimeRange(qMax(r.in(), rational(i)),
|
||||
qMin(r.out(), rational(i + chunk_size))));
|
||||
qMin(r.out(), rational(i + chunk_size))));
|
||||
}
|
||||
|
||||
return split_ranges;
|
||||
@@ -187,270 +192,234 @@ QList<TimeRange> RenderBackend::SplitRangeIntoChunks(const TimeRange &r)
|
||||
|
||||
void RenderBackend::NodeGraphChanged(NodeInput *source)
|
||||
{
|
||||
video_pool_.Queue(source);
|
||||
audio_pool_.Queue(source);
|
||||
hash_pool_.Queue(source);
|
||||
}
|
||||
if (!graph_update_queue_.isEmpty()) {
|
||||
// First, check if anything in our queue is a dependency of this input. If so, we should remove
|
||||
// it and just update this input.
|
||||
|
||||
void RenderBackend::UpdateInstance(RenderWorker *instance)
|
||||
{
|
||||
instance->SetAvailable(false);
|
||||
instance->ProcessQueue();
|
||||
instance->SetVideoParams(video_params_);
|
||||
instance->SetAudioParams(audio_params_);
|
||||
instance->SetVideoDownloadMatrix(video_download_matrix_);
|
||||
instance->SetAudioModeIsPreview(auto_audio_);
|
||||
// First we need to find our copy of the input being queued
|
||||
Node* our_copy_node = copy_map_.value(source->parentNode());
|
||||
|
||||
if (our_copy_node) {
|
||||
NodeInput* our_copy = our_copy_node->GetInputWithID(source->id());
|
||||
QList<Node*> our_copy_deps = our_copy->GetDependencies(our_copy);
|
||||
|
||||
for (int i=0;i<graph_update_queue_.size();i++) {
|
||||
NodeInput* check_input = graph_update_queue_.at(i);
|
||||
Node* check_input_our_copy = copy_map_.value(check_input->parentNode());
|
||||
|
||||
// If this input isn't connected anymore, it obviously won't come up as a dependency
|
||||
if (our_copy_deps.contains(check_input_our_copy)) {
|
||||
graph_update_queue_.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
graph_update_queue_.append(source);
|
||||
}
|
||||
|
||||
void RenderBackend::Close()
|
||||
{
|
||||
video_pool_.threads.clear();
|
||||
audio_pool_.threads.clear();
|
||||
hash_pool_.threads.clear();
|
||||
SetViewerNode(nullptr);
|
||||
|
||||
CancelQueue();
|
||||
|
||||
video_pool_.Destroy();
|
||||
audio_pool_.Destroy();
|
||||
hash_pool_.Destroy();
|
||||
for (int i=0;i<workers_.size();i++) {
|
||||
workers_.at(i).worker->deleteLater();
|
||||
}
|
||||
workers_.clear();
|
||||
}
|
||||
|
||||
RenderWorker *RenderBackend::GetInstanceFromPool(RenderPool& pool)
|
||||
void RenderBackend::RunNextJob()
|
||||
{
|
||||
RenderWorker* instance = nullptr;
|
||||
|
||||
foreach (RenderWorker* worker, pool.instances) {
|
||||
if (worker->IsAvailable()) {
|
||||
instance = worker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!instance) {
|
||||
if (pool.instances.size() < pool.threads.maxThreadCount()) {
|
||||
// Can create another instance
|
||||
instance = CreateNewWorker();
|
||||
pool.instances.append(instance);
|
||||
|
||||
if (viewer_node_) {
|
||||
instance->Init(viewer_node_);
|
||||
}
|
||||
|
||||
connect(instance, &RenderWorker::FinishedJob,
|
||||
this, &RenderBackend::WorkerFinished, Qt::QueuedConnection);
|
||||
|
||||
connect(instance, &RenderWorker::AudioConformUnavailable,
|
||||
this, &RenderBackend::AudioConformUnavailable, Qt::QueuedConnection);
|
||||
} else {
|
||||
instance = pool.instances.at(pool.queuer % pool.instances.size());
|
||||
pool.queuer++;
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void RenderBackend::ListenForConformSignal(AudioStreamPtr s)
|
||||
{
|
||||
foreach (const ConformWaitInfo& info, conform_wait_info_) {
|
||||
if (info.stream == s) {
|
||||
// We've probably already connected to this one
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connect(s.get(), &AudioStream::ConformAppended, this, &RenderBackend::AudioConformUpdated);
|
||||
}
|
||||
|
||||
void RenderBackend::StopListeningForConformSignal(AudioStream *s)
|
||||
{
|
||||
foreach (const ConformWaitInfo& info, conform_wait_info_) {
|
||||
if (info.stream.get() == s) {
|
||||
// There are still conforms we're waiting for, don't disconnect
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(s, &AudioStream::ConformAppended, this, &RenderBackend::AudioConformUpdated);
|
||||
}
|
||||
|
||||
void RenderBackend::AudioConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params)
|
||||
{
|
||||
ConformWaitInfo info = {stream, params, range, stream_time};
|
||||
|
||||
if (conform_wait_info_.contains(info)) {
|
||||
// If queue is empty, nothing to be done
|
||||
if (render_queue_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(stream);
|
||||
|
||||
if (audio_stream->try_start_conforming(params)) {
|
||||
|
||||
// Start indexing process
|
||||
ListenForConformSignal(audio_stream);
|
||||
|
||||
conform_wait_info_.append(info);
|
||||
|
||||
ConformTask* conform_task = new ConformTask(audio_stream, params);
|
||||
|
||||
TaskManager::instance()->AddTask(conform_task);
|
||||
|
||||
} else if (audio_stream->has_conformed_version(params)) {
|
||||
|
||||
// Conform JUST finished, requeue this time
|
||||
ic_from_conform_ = true;
|
||||
AudioInvalidated(range);
|
||||
ic_from_conform_ = false;
|
||||
|
||||
} else {
|
||||
|
||||
// A conform task is already running, so we'll just wait for it
|
||||
ListenForConformSignal(audio_stream);
|
||||
|
||||
conform_wait_info_.append(info);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::AudioConformUpdated(AudioRenderingParams params)
|
||||
{
|
||||
AudioStream *stream = static_cast<AudioStream*>(sender());
|
||||
|
||||
for (int i=0;i<conform_wait_info_.size();i++) {
|
||||
const ConformWaitInfo& info = conform_wait_info_.at(i);
|
||||
|
||||
if (info.stream.get() == stream
|
||||
&& info.params == params) {
|
||||
|
||||
// Make a copy so the values we use aren't corrupt
|
||||
ConformWaitInfo copy = info;
|
||||
|
||||
// Remove this entry from the list
|
||||
conform_wait_info_.removeAt(i);
|
||||
i--;
|
||||
|
||||
// Send invalidate cache signal
|
||||
ic_from_conform_ = true;
|
||||
AudioInvalidated(copy.affected_range);
|
||||
ic_from_conform_ = false;
|
||||
|
||||
}
|
||||
// Check if params are valid
|
||||
if (!video_params_.is_valid()
|
||||
|| !audio_params_.is_valid()) {
|
||||
qDebug() << "Failed to run job, parameters are invalid";
|
||||
return;
|
||||
}
|
||||
|
||||
StopListeningForConformSignal(stream);
|
||||
}
|
||||
// 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;
|
||||
|
||||
void RenderBackend::AudioInvalidated(const TimeRange& r)
|
||||
{
|
||||
if (!ic_from_conform_) {
|
||||
// Cancel any ranges waiting on a conform here since obviously the contents have changed
|
||||
for (int i=0;i<conform_wait_info_.size();i++) {
|
||||
ConformWaitInfo& info = conform_wait_info_[i];
|
||||
|
||||
// FIXME: Code shamelessly copied from TimeRangeList::RemoveTimeRange()
|
||||
|
||||
if (r.Contains(info.affected_range)) {
|
||||
conform_wait_info_.removeAt(i);
|
||||
i--;
|
||||
} else if (info.affected_range.Contains(r, false, false)) {
|
||||
ConformWaitInfo copy = info;
|
||||
|
||||
info.affected_range.set_out(r.in());
|
||||
copy.affected_range.set_in(r.out());
|
||||
|
||||
conform_wait_info_.append(copy);
|
||||
} else if (info.affected_range.in() < r.in() && info.affected_range.out() > r.in()) {
|
||||
info.affected_range.set_out(r.in());
|
||||
} else if (info.affected_range.in() < r.out() && info.affected_range.out() > r.out()) {
|
||||
info.affected_range.set_in(r.out());
|
||||
foreach (const WorkerData& data, workers_) {
|
||||
if (data.busy) {
|
||||
all_workers_available = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Split into 2 second chunks, one for each thread
|
||||
QList<TimeRange> split_ranges = SplitRangeIntoChunks(r);
|
||||
|
||||
foreach (const TimeRange& this_range, split_ranges) {
|
||||
|
||||
{
|
||||
// Check if this range is already in the queue but hasn't started yet, in which case it'll
|
||||
// automatically update to the parameters we have now anyway and we don't need to queue again
|
||||
QMutexLocker locker(&queued_audio_lock_);
|
||||
if (queued_audio_.ContainsTimeRange(this_range)) {
|
||||
continue;
|
||||
}
|
||||
queued_audio_.InsertTimeRange(this_range);
|
||||
}
|
||||
|
||||
// Queue this range
|
||||
QFutureWatcher<SampleBufferPtr>* watcher = new QFutureWatcher<SampleBufferPtr>();
|
||||
connect(watcher, &QFutureWatcher<SampleBufferPtr>::finished,
|
||||
this, &RenderBackend::AudioRendered);
|
||||
|
||||
audio_jobs_.insert(watcher, this_range);
|
||||
|
||||
watcher->setFuture(RenderAudio(this_range, auto_audio_));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::AudioRendered()
|
||||
{
|
||||
QFutureWatcher<SampleBufferPtr>* watcher = static_cast<QFutureWatcher<SampleBufferPtr>*>(sender());
|
||||
|
||||
if (audio_jobs_.contains(watcher)) {
|
||||
TimeRange r = audio_jobs_.take(watcher);
|
||||
|
||||
if (watcher->result()) {
|
||||
viewer_node_->audio_playback_cache()->WritePCM(r, watcher->result());
|
||||
if (all_workers_available) {
|
||||
// Process queue
|
||||
ProcessUpdateQueue();
|
||||
} else {
|
||||
viewer_node_->audio_playback_cache()->WriteSilence(r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
watcher->deleteLater();
|
||||
// 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->SetCopyMap(©_map_);
|
||||
|
||||
RenderTicketPtr ticket = render_queue_.takeFirst();
|
||||
|
||||
switch (ticket->GetType()) {
|
||||
case RenderTicket::kTypeVideo:
|
||||
QtConcurrent::run(worker,
|
||||
&RenderWorker::RenderFrame,
|
||||
ticket,
|
||||
copied_viewer_node_,
|
||||
ticket->GetTime().in());
|
||||
break;
|
||||
case RenderTicket::kTypeAudio:
|
||||
QtConcurrent::run(worker,
|
||||
&RenderWorker::RenderAudio,
|
||||
ticket,
|
||||
copied_viewer_node_,
|
||||
ticket->GetTime());
|
||||
break;
|
||||
}
|
||||
|
||||
if (render_queue_.isEmpty()) {
|
||||
// No more jobs, can exit here
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::ProcessUpdateQueue()
|
||||
{
|
||||
while (!graph_update_queue_.isEmpty()) {
|
||||
CopyNodeInputValue(graph_update_queue_.takeFirst());
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::WorkerFinished()
|
||||
{
|
||||
static_cast<RenderWorker*>(sender())->SetAvailable(true);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
RunNextJob();
|
||||
}
|
||||
|
||||
bool RenderBackend::ConformWaitInfo::operator==(const RenderBackend::ConformWaitInfo &rhs) const
|
||||
void RenderBackend::CopyNodeInputValue(NodeInput *input)
|
||||
{
|
||||
return rhs.stream == stream
|
||||
&& rhs.stream_time == stream_time
|
||||
&& rhs.affected_range == affected_range;
|
||||
}
|
||||
// Find our copy of this parameter
|
||||
Node* our_copy_node = copy_map_.value(input->parentNode());
|
||||
NodeInput* our_copy = our_copy_node->GetInputWithID(input->id());
|
||||
|
||||
RenderBackend::RenderPool::RenderPool() :
|
||||
queuer(0)
|
||||
{
|
||||
}
|
||||
// Copy the standard/keyframe values between these two inputs
|
||||
NodeInput::CopyValues(input,
|
||||
our_copy,
|
||||
false);
|
||||
|
||||
void RenderBackend::RenderPool::Init(ViewerOutput* v)
|
||||
{
|
||||
foreach (RenderWorker* instance, instances) {
|
||||
instance->Init(v);
|
||||
// Handle connections
|
||||
if (input->IsConnected() || our_copy->IsConnected()) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::RenderPool::Queue(NodeInput *input)
|
||||
Node* RenderBackend::CopyNodeConnections(Node* src_node)
|
||||
{
|
||||
foreach (RenderWorker* worker, instances) {
|
||||
worker->Queue(input);
|
||||
// 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::RenderPool::Destroy()
|
||||
void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_input)
|
||||
{
|
||||
qDeleteAll(instances);
|
||||
instances.clear();
|
||||
}
|
||||
if (src_input->IsConnected()) {
|
||||
Node* dst_node = CopyNodeConnections(src_input->get_connected_node());
|
||||
|
||||
void RenderBackend::RenderPool::Close()
|
||||
{
|
||||
foreach (RenderWorker* worker, instances) {
|
||||
worker->Close();
|
||||
NodeOutput* corresponding_output = dst_node->GetOutputWithID(src_input->get_connected_output()->id());
|
||||
|
||||
NodeParam::ConnectEdge(corresponding_output,
|
||||
dst_input);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "node/graph.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "render/backend/colorprocessorcache.h"
|
||||
#include "renderticket.h"
|
||||
#include "renderworker.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
@@ -42,22 +43,26 @@ public:
|
||||
|
||||
void SetViewerNode(ViewerOutput* viewer_node);
|
||||
|
||||
void CancelQueue();
|
||||
void SetUpdateWithGraph(bool e);
|
||||
|
||||
void ClearVideoQueue();
|
||||
|
||||
/**
|
||||
* @brief Asynchronously generate a hash at a given time
|
||||
*/
|
||||
QFuture<QByteArray> Hash(const rational& time, bool block_for_update);
|
||||
QFuture<QList<QByteArray> > Hash(const QList<rational>& times);
|
||||
|
||||
/**
|
||||
* @brief Asynchronously generate a frame at a given time
|
||||
*/
|
||||
QFuture<FramePtr> RenderFrame(const rational& time, bool clear_queue, bool block_for_update);
|
||||
RenderTicketPtr RenderFrame(const rational& time);
|
||||
|
||||
QFuture<FramePtr> RenderFrames(const QList<rational>& frames);
|
||||
|
||||
/**
|
||||
* @brief Asynchronously generate a chunk of audio
|
||||
*/
|
||||
QFuture<SampleBufferPtr> RenderAudio(const TimeRange& r, bool block_for_update);
|
||||
QFuture<SampleBufferPtr> RenderAudio(const TimeRange& r);
|
||||
|
||||
void SetVideoParams(const VideoRenderingParams& params);
|
||||
|
||||
@@ -65,86 +70,52 @@ public:
|
||||
|
||||
void SetVideoDownloadMatrix(const QMatrix4x4& mat);
|
||||
|
||||
void SetAutomaticAudio(bool e);
|
||||
|
||||
void WorkerStartedRenderingAudio(const TimeRange& r);
|
||||
|
||||
static QList<TimeRange> SplitRangeIntoChunks(const TimeRange& r);
|
||||
|
||||
public slots:
|
||||
void NodeGraphChanged(NodeInput *source);
|
||||
|
||||
void UpdateInstance(OLIVE_NAMESPACE::RenderWorker* instance);
|
||||
|
||||
protected:
|
||||
virtual RenderWorker* CreateNewWorker() = 0;
|
||||
|
||||
void Close();
|
||||
|
||||
private:
|
||||
struct RenderPool {
|
||||
RenderPool();
|
||||
void CopyNodeInputValue(NodeInput* input);
|
||||
Node *CopyNodeConnections(Node *src_node);
|
||||
void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input);
|
||||
|
||||
QVector<RenderWorker*> instances;
|
||||
int queuer;
|
||||
QThreadPool threads;
|
||||
void RunNextJob();
|
||||
|
||||
void Init(ViewerOutput *v);
|
||||
void Queue(NodeInput* input);
|
||||
void Close();
|
||||
void Destroy();
|
||||
};
|
||||
|
||||
RenderWorker *GetInstanceFromPool(RenderPool &pool);
|
||||
void ProcessUpdateQueue();
|
||||
|
||||
ViewerOutput* viewer_node_;
|
||||
|
||||
//RenderCancelDialog* cancel_dialog_;
|
||||
|
||||
RenderPool video_pool_;
|
||||
RenderPool audio_pool_;
|
||||
RenderPool hash_pool_;
|
||||
|
||||
QMutex queued_audio_lock_;
|
||||
TimeRangeList queued_audio_;
|
||||
|
||||
bool auto_audio_;
|
||||
|
||||
// VIDEO MEMBERS
|
||||
VideoRenderingParams video_params_;
|
||||
QMatrix4x4 video_download_matrix_;
|
||||
|
||||
// AUDIO MEMBERS
|
||||
AudioRenderingParams audio_params_;
|
||||
QHash< QFutureWatcher<SampleBufferPtr>*, TimeRange > audio_jobs_;
|
||||
|
||||
struct ConformWaitInfo {
|
||||
StreamPtr stream;
|
||||
AudioRenderingParams params;
|
||||
TimeRange affected_range;
|
||||
rational stream_time;
|
||||
QList<NodeInput*> graph_update_queue_;
|
||||
QHash<Node*, Node*> copy_map_;
|
||||
ViewerOutput* copied_viewer_node_;
|
||||
|
||||
bool operator==(const ConformWaitInfo& rhs) const;
|
||||
QThreadPool pool_;
|
||||
|
||||
QLinkedList<RenderTicketPtr> render_queue_;
|
||||
|
||||
struct WorkerData {
|
||||
RenderWorker* worker;
|
||||
bool busy;
|
||||
};
|
||||
|
||||
QList<ConformWaitInfo> conform_wait_info_;
|
||||
QVector<WorkerData> workers_;
|
||||
|
||||
void ListenForConformSignal(AudioStreamPtr s);
|
||||
|
||||
void StopListeningForConformSignal(AudioStream *s);
|
||||
|
||||
bool ic_from_conform_;
|
||||
bool update_with_graph_;
|
||||
|
||||
private slots:
|
||||
void AudioConformUnavailable(StreamPtr stream, TimeRange range,
|
||||
rational stream_time, AudioRenderingParams params);
|
||||
|
||||
void AudioConformUpdated(OLIVE_NAMESPACE::AudioRenderingParams params);
|
||||
|
||||
void AudioInvalidated(const TimeRange &r);
|
||||
|
||||
void AudioRendered();
|
||||
|
||||
void WorkerFinished();
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/***
|
||||
|
||||
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 "renderticket.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
RenderTicket::RenderTicket(Type type, const TimeRange &time) :
|
||||
finished_(false),
|
||||
cancelled_(false),
|
||||
time_(time),
|
||||
type_(type)
|
||||
{
|
||||
}
|
||||
|
||||
void RenderTicket::WaitForFinished()
|
||||
{
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
if (!finished_) {
|
||||
wait_.wait(&lock_);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant RenderTicket::Get()
|
||||
{
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
if (!finished_) {
|
||||
wait_.wait(&lock_);
|
||||
}
|
||||
|
||||
return result_;
|
||||
}
|
||||
|
||||
bool RenderTicket::IsFinished(bool lock)
|
||||
{
|
||||
if (lock) {
|
||||
lock_.lock();
|
||||
}
|
||||
|
||||
bool finished = finished_;
|
||||
|
||||
if (lock) {
|
||||
lock_.unlock();
|
||||
}
|
||||
|
||||
return finished;
|
||||
}
|
||||
|
||||
bool RenderTicket::WasCancelled()
|
||||
{
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
return cancelled_;
|
||||
}
|
||||
|
||||
void RenderTicket::Finish(QVariant result)
|
||||
{
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
finished_ = true;
|
||||
result_ = result;
|
||||
|
||||
wait_.wakeAll();
|
||||
|
||||
locker.unlock();
|
||||
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
void RenderTicket::Cancel()
|
||||
{
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
finished_ = true;
|
||||
cancelled_ = true;
|
||||
|
||||
wait_.wakeAll();
|
||||
|
||||
locker.unlock();
|
||||
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,94 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef RENDERTICKET_H
|
||||
#define RENDERTICKET_H
|
||||
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "codec/samplebuffer.h"
|
||||
#include "common/timerange.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class RenderTicket : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Type {
|
||||
kTypeVideo,
|
||||
kTypeAudio
|
||||
};
|
||||
|
||||
RenderTicket(Type type, const TimeRange& time);
|
||||
|
||||
const TimeRange& GetTime() const
|
||||
{
|
||||
return time_;
|
||||
}
|
||||
|
||||
Type GetType() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
void WaitForFinished();
|
||||
|
||||
QVariant Get();
|
||||
|
||||
bool IsFinished(bool lock = true);
|
||||
|
||||
bool WasCancelled();
|
||||
|
||||
QMutex* lock()
|
||||
{
|
||||
return &lock_;
|
||||
}
|
||||
|
||||
void Finish(QVariant result);
|
||||
|
||||
void Cancel();
|
||||
|
||||
signals:
|
||||
void Finished();
|
||||
|
||||
private:
|
||||
bool finished_;
|
||||
|
||||
bool cancelled_;
|
||||
|
||||
QVariant result_;
|
||||
|
||||
QMutex lock_;
|
||||
|
||||
QWaitCondition wait_;
|
||||
|
||||
TimeRange time_;
|
||||
|
||||
Type type_;
|
||||
|
||||
};
|
||||
|
||||
using RenderTicketPtr = std::shared_ptr<RenderTicket>;
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // RENDERTICKET_H
|
||||
@@ -0,0 +1,82 @@
|
||||
/***
|
||||
|
||||
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 "renderticketwatcher.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
RenderTicketWatcher::RenderTicketWatcher(QObject *parent) :
|
||||
QObject(parent),
|
||||
ticket_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void RenderTicketWatcher::SetTicket(RenderTicketPtr ticket)
|
||||
{
|
||||
// Ensure that a ticket has NOT already been set and that this ticket is NOT NULL
|
||||
Q_ASSERT(!ticket_ && ticket);
|
||||
|
||||
ticket_ = ticket;
|
||||
|
||||
QMutexLocker locker(ticket->lock());
|
||||
|
||||
if (ticket_->IsFinished(false)) {
|
||||
locker.unlock();
|
||||
emit Finished();
|
||||
} else {
|
||||
connect(ticket_.get(), &RenderTicket::Finished, this, &RenderTicketWatcher::Finished);
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderTicketWatcher::WasCancelled()
|
||||
{
|
||||
if (ticket_) {
|
||||
return ticket_->WasCancelled();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderTicketWatcher::IsFinished()
|
||||
{
|
||||
if (ticket_) {
|
||||
return ticket_->IsFinished();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderTicketWatcher::WaitForFinished()
|
||||
{
|
||||
if (ticket_) {
|
||||
ticket_->WaitForFinished();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant RenderTicketWatcher::Get()
|
||||
{
|
||||
if (ticket_) {
|
||||
return ticket_->Get();
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,54 @@
|
||||
/***
|
||||
|
||||
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/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef RENDERTICKETWATCHER_H
|
||||
#define RENDERTICKETWATCHER_H
|
||||
|
||||
#include "renderticket.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class RenderTicketWatcher : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RenderTicketWatcher(QObject* parent = nullptr);
|
||||
|
||||
void SetTicket(RenderTicketPtr ticket);
|
||||
|
||||
bool WasCancelled();
|
||||
|
||||
bool IsFinished();
|
||||
|
||||
void WaitForFinished();
|
||||
|
||||
QVariant Get();
|
||||
|
||||
signals:
|
||||
void Finished();
|
||||
|
||||
private:
|
||||
RenderTicketPtr ticket_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // RENDERTICKETWATCHER_H
|
||||
@@ -33,53 +33,14 @@ OLIVE_NAMESPACE_ENTER
|
||||
|
||||
RenderWorker::RenderWorker(RenderBackend* parent) :
|
||||
parent_(parent),
|
||||
viewer_(nullptr),
|
||||
available_(true),
|
||||
audio_mode_is_preview_(false)
|
||||
{
|
||||
}
|
||||
|
||||
RenderWorker::~RenderWorker()
|
||||
void RenderWorker::RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, const rational &time)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
QByteArray RenderWorker::Hash(const rational &time, bool block_for_update)
|
||||
{
|
||||
if (!viewer_) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
UpdateData(block_for_update);
|
||||
|
||||
QCryptographicHash hasher(QCryptographicHash::Sha1);
|
||||
|
||||
// Embed video parameters into this hash
|
||||
hasher.addData(reinterpret_cast<const char*>(&video_params_.effective_width()), sizeof(int));
|
||||
hasher.addData(reinterpret_cast<const char*>(&video_params_.effective_height()), sizeof(int));
|
||||
hasher.addData(reinterpret_cast<const char*>(&video_params_.format()), sizeof(PixelFormat::Format));
|
||||
hasher.addData(reinterpret_cast<const char*>(&video_params_.mode()), sizeof(RenderMode::Mode));
|
||||
|
||||
viewer_->Hash(hasher, time);
|
||||
|
||||
emit FinishedJob();
|
||||
|
||||
return hasher.result();
|
||||
}
|
||||
|
||||
FramePtr RenderWorker::RenderFrame(const rational &time, bool block_for_update)
|
||||
{
|
||||
if (!viewer_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
UpdateData(block_for_update);
|
||||
|
||||
NodeValueTable table = ProcessInput(viewer_->texture_input(),
|
||||
NodeValueTable table = ProcessInput(viewer->texture_input(),
|
||||
TimeRange(time, time + video_params_.time_base()));
|
||||
|
||||
QVariant texture = table.Get(NodeParam::kTexture);
|
||||
@@ -97,44 +58,20 @@ FramePtr RenderWorker::RenderFrame(const rational &time, bool block_for_update)
|
||||
TextureToFrame(texture, frame, video_download_matrix_);
|
||||
}
|
||||
|
||||
emit FinishedJob();
|
||||
ticket->Finish(QVariant::fromValue(frame));
|
||||
|
||||
return frame;
|
||||
FinishedJob();
|
||||
}
|
||||
|
||||
SampleBufferPtr RenderWorker::RenderAudio(const TimeRange &range, bool block_for_update)
|
||||
void RenderWorker::RenderAudio(RenderTicketPtr ticket, ViewerOutput* viewer, const TimeRange &range)
|
||||
{
|
||||
if (!viewer_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
parent_->WorkerStartedRenderingAudio(range);
|
||||
|
||||
UpdateData(block_for_update);
|
||||
|
||||
audio_render_time_ = range;
|
||||
|
||||
NodeValueTable table = ProcessInput(viewer_->samples_input(), range);
|
||||
NodeValueTable table = ProcessInput(viewer->samples_input(), range);
|
||||
|
||||
QVariant samples = table.Get(NodeParam::kSamples);
|
||||
|
||||
return samples.value<SampleBufferPtr>();
|
||||
}
|
||||
ticket->Finish(samples);
|
||||
|
||||
void RenderWorker::UpdateData(bool block_for_update)
|
||||
{
|
||||
// FIXME: This is pretty trashy. It works, but it's not good. Should probably be changed at some
|
||||
// point.
|
||||
if (block_for_update) {
|
||||
QMetaObject::invokeMethod(parent_,
|
||||
"UpdateInstance",
|
||||
Qt::BlockingQueuedConnection,
|
||||
OLIVE_NS_ARG(RenderWorker*, this));
|
||||
} else {
|
||||
parent_->UpdateInstance(this);
|
||||
}
|
||||
FinishedJob();
|
||||
}
|
||||
|
||||
NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const TimeRange &range)
|
||||
@@ -188,7 +125,7 @@ NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const
|
||||
|
||||
{
|
||||
// Save waveform to file
|
||||
Block* src_block = static_cast<Block*>(copy_map_.key(b));
|
||||
Block* src_block = static_cast<Block*>(copy_map_->key(b));
|
||||
QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
|
||||
QDir waveform_loc = local_appdata_dir.filePath(QStringLiteral("waveform"));
|
||||
waveform_loc.mkpath(".");
|
||||
@@ -444,143 +381,4 @@ DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream)
|
||||
return decoder;
|
||||
}
|
||||
|
||||
void RenderWorker::Queue(NodeInput *input)
|
||||
{
|
||||
if (!queued_updates_.isEmpty()) {
|
||||
// First, check if anything in our queue is a dependency of this input. If so, we should remove
|
||||
// it and just update this input.
|
||||
|
||||
// First we need to find our copy of the input being queued
|
||||
Node* our_copy_node = copy_map_.value(input->parentNode());
|
||||
|
||||
if (our_copy_node) {
|
||||
NodeInput* our_copy = our_copy_node->GetInputWithID(input->id());
|
||||
QList<Node*> our_copy_deps = our_copy->GetDependencies(our_copy);
|
||||
|
||||
for (int i=0;i<queued_updates_.size();i++) {
|
||||
NodeInput* check_input = queued_updates_.at(i);
|
||||
Node* check_input_our_copy = copy_map_.value(check_input->parentNode());
|
||||
|
||||
// If this input isn't connected anymore, it obviously won't come up as a dependency
|
||||
if (our_copy_deps.contains(check_input_our_copy)) {
|
||||
queued_updates_.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queued_updates_.append(input);
|
||||
}
|
||||
|
||||
void RenderWorker::ProcessQueue()
|
||||
{
|
||||
while (!queued_updates_.isEmpty()) {
|
||||
CopyNodeInputValue(queued_updates_.takeFirst());
|
||||
}
|
||||
}
|
||||
|
||||
void RenderWorker::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);
|
||||
|
||||
// Handle connections
|
||||
if (input->IsConnected() || our_copy->IsConnected()) {
|
||||
// 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* RenderWorker::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 RenderWorker::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_input)
|
||||
{
|
||||
if (src_input->IsConnected()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderWorker::Init(ViewerOutput* viewer)
|
||||
{
|
||||
viewer_ = static_cast<ViewerOutput*>(viewer->copy());
|
||||
copy_map_.insert(viewer, viewer_);
|
||||
|
||||
Queue(viewer->texture_input());
|
||||
Queue(viewer->samples_input());
|
||||
ProcessQueue();
|
||||
}
|
||||
|
||||
void RenderWorker::Close()
|
||||
{
|
||||
// Delete all the nodes
|
||||
qDeleteAll(copy_map_);
|
||||
copy_map_.clear();
|
||||
|
||||
viewer_ = nullptr;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "decodercache.h"
|
||||
#include "node/traverser.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "renderticket.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -37,14 +38,6 @@ class RenderWorker : public QObject, public NodeTraverser
|
||||
public:
|
||||
RenderWorker(RenderBackend* parent);
|
||||
|
||||
virtual ~RenderWorker() override;
|
||||
|
||||
void Init(ViewerOutput *viewer);
|
||||
void Close();
|
||||
|
||||
void Queue(NodeInput* input);
|
||||
void ProcessQueue();
|
||||
|
||||
bool IsAvailable() const
|
||||
{
|
||||
return available_;
|
||||
@@ -55,11 +48,6 @@ public:
|
||||
available_ = a;
|
||||
}
|
||||
|
||||
ViewerOutput* GetViewer() const
|
||||
{
|
||||
return viewer_;
|
||||
}
|
||||
|
||||
void SetVideoParams(const VideoRenderingParams& params)
|
||||
{
|
||||
video_params_ = params;
|
||||
@@ -80,6 +68,11 @@ public:
|
||||
audio_mode_is_preview_ = audio_mode_is_preview;
|
||||
}
|
||||
|
||||
void SetCopyMap(QHash<Node*, Node*>* copy_map)
|
||||
{
|
||||
copy_map_ = copy_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a unique ID for the image generated at this time
|
||||
*
|
||||
@@ -90,7 +83,7 @@ public:
|
||||
*
|
||||
* SHA-1 hash or empty QByteArray if no viewer node is set.
|
||||
*/
|
||||
QByteArray Hash(const rational &time, bool block_for_update);
|
||||
void Hash(RenderTicketPtr ticket, ViewerOutput *viewer, const QList<rational>& times);
|
||||
|
||||
/**
|
||||
* @brief Render the frame at this time
|
||||
@@ -103,9 +96,9 @@ public:
|
||||
* function will still return a blank frame with the same parameters. If no viewer node is set,
|
||||
* nullptr is returned.
|
||||
*/
|
||||
FramePtr RenderFrame(const rational& time, bool block_for_update);
|
||||
void RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, const rational &time);
|
||||
|
||||
SampleBufferPtr RenderAudio(const TimeRange& range, bool block_for_update);
|
||||
void RenderAudio(RenderTicketPtr ticket, ViewerOutput* viewer, const TimeRange& range);
|
||||
|
||||
protected:
|
||||
virtual void TextureToFrame(const QVariant& texture, FramePtr frame, const QMatrix4x4 &mat) const = 0;
|
||||
@@ -135,16 +128,10 @@ signals:
|
||||
void FinishedJob();
|
||||
|
||||
private:
|
||||
void UpdateData(bool block_for_update);
|
||||
|
||||
NodeValue GetDataFromStream(StreamPtr stream, const TimeRange& input_time);
|
||||
|
||||
DecoderPtr ResolveDecoderFromInput(StreamPtr stream);
|
||||
|
||||
void CopyNodeInputValue(NodeInput* input);
|
||||
Node *CopyNodeConnections(Node *src_node);
|
||||
void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input);
|
||||
|
||||
RenderBackend* parent_;
|
||||
|
||||
VideoRenderingParams video_params_;
|
||||
@@ -165,15 +152,12 @@ private:
|
||||
|
||||
DecoderCache decoder_cache_;
|
||||
|
||||
ViewerOutput* viewer_;
|
||||
TimeRange audio_render_time_;
|
||||
QList<NodeInput*> queued_updates_;
|
||||
QHash<Node*, Node*> copy_map_;
|
||||
bool available_;
|
||||
|
||||
bool audio_mode_is_preview_;
|
||||
|
||||
QMutex lock_;
|
||||
QHash<Node*, Node*>* copy_map_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -48,7 +48,7 @@ bool CacheTask::Run()
|
||||
}
|
||||
}
|
||||
|
||||
Render(range_to_cache, QMatrix4x4(), false);
|
||||
Render(range_to_cache, QMatrix4x4(), false, true);
|
||||
|
||||
download_threads_.waitForDone();
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ bool ExportTask::Run()
|
||||
}
|
||||
|
||||
// Start render process
|
||||
Render({range}, mat, params_.audio_enabled());
|
||||
Render({range}, mat, params_.audio_enabled(), false);
|
||||
|
||||
bool success = true;
|
||||
|
||||
|
||||
+42
-41
@@ -34,7 +34,7 @@ RenderTask::RenderTask(ViewerOutput* viewer, const VideoRenderingParams &vparams
|
||||
|
||||
struct TimeHashFuturePair {
|
||||
rational time;
|
||||
QFuture<QByteArray> hash_future;
|
||||
RenderTicketPtr hash_future;
|
||||
};
|
||||
|
||||
struct HashTimePair {
|
||||
@@ -44,12 +44,12 @@ struct HashTimePair {
|
||||
|
||||
struct HashFrameFuturePair {
|
||||
QByteArray hash;
|
||||
QFuture<FramePtr> frame_future;
|
||||
RenderTicketPtr frame_future;
|
||||
};
|
||||
|
||||
struct RangeSampleFuturePair {
|
||||
TimeRange range;
|
||||
QFuture<SampleBufferPtr> sample_future;
|
||||
RenderTicketPtr sample_future;
|
||||
};
|
||||
|
||||
struct HashDownloadFuturePair {
|
||||
@@ -59,31 +59,42 @@ struct HashDownloadFuturePair {
|
||||
|
||||
void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
const QMatrix4x4& mat,
|
||||
bool audio_enabled)
|
||||
bool audio_enabled,
|
||||
bool use_disk_cache)
|
||||
{
|
||||
OpenGLBackend backend;
|
||||
|
||||
backend.SetAutomaticAudio(false);
|
||||
backend.moveToThread(qApp->thread());
|
||||
|
||||
backend.SetViewerNode(viewer_);
|
||||
backend.SetVideoParams(video_params_);
|
||||
backend.SetAudioParams(audio_params_);
|
||||
backend.SetVideoDownloadMatrix(mat);
|
||||
|
||||
// Get hashes for each frame
|
||||
QLinkedList<TimeHashFuturePair> hash_list;
|
||||
// Get hashes for each frame and group likes together
|
||||
QMap< QByteArray, QLinkedList<rational> > times_to_render;
|
||||
|
||||
{
|
||||
QList<rational> times = viewer_->video_frame_cache()->GetFrameListFromTimeRange(range_to_cache);
|
||||
|
||||
foreach (const rational& r, times) {
|
||||
hash_list.append({r, backend.Hash(r, false)});
|
||||
}
|
||||
}
|
||||
QFuture<QList<QByteArray> > hash_future = backend.Hash(times);
|
||||
QList<QByteArray> hashes = hash_future.result();
|
||||
|
||||
// Determine any duplicates
|
||||
QMap< QByteArray, QLinkedList<rational> > times_to_render;
|
||||
foreach (const TimeHashFuturePair& i, hash_list) {
|
||||
times_to_render[i.hash_future.result()].append(i.time);
|
||||
// Determine any duplicates
|
||||
int index = 0;
|
||||
foreach (const QByteArray& hash, hashes) {
|
||||
const rational& time = times.at(index);
|
||||
|
||||
if (use_disk_cache
|
||||
&& QFileInfo::exists(viewer_->video_frame_cache()->CachePathName(hash, video_params_.format()))) {
|
||||
// Already exists, no need to render it again
|
||||
FrameDownloaded(hash, {time});
|
||||
} else {
|
||||
times_to_render[hash].append(time);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Render all frames necessary
|
||||
@@ -96,35 +107,25 @@ void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
QMap< QByteArray, QLinkedList<rational> >::const_iterator i;
|
||||
for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) {
|
||||
const QByteArray& hash = i.key();
|
||||
const rational& time = i.value().first();
|
||||
|
||||
if (QFileInfo::exists(viewer_->video_frame_cache()->CachePathName(hash, video_params_.format()))) {
|
||||
bool inserted = false;
|
||||
|
||||
// Already exists, no need to render it again
|
||||
FrameDownloaded(hash, i.value());
|
||||
|
||||
} else {
|
||||
|
||||
const rational& time = i.value().first();
|
||||
|
||||
bool inserted = false;
|
||||
|
||||
for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) {
|
||||
if (sorted_iterator->time > time) {
|
||||
sorted_times.insert(sorted_iterator, {time, hash});
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
sorted_times.append({time, hash});
|
||||
for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) {
|
||||
if (sorted_iterator->time > time) {
|
||||
sorted_times.insert(sorted_iterator, {time, hash});
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
sorted_times.append({time, hash});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const HashTimePair& p, sorted_times) {
|
||||
render_lookup_table.append({p.hash, backend.RenderFrame(p.time, false, false)});
|
||||
render_lookup_table.append({p.hash, backend.RenderFrame(p.time)});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +135,7 @@ void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
QList<TimeRange> ranges = RenderBackend::SplitRangeIntoChunks(r);
|
||||
|
||||
foreach (const TimeRange& split, ranges) {
|
||||
audio_lookup_table.append({split, backend.RenderAudio(split, false)});
|
||||
audio_lookup_table.append({split, backend.RenderAudio(split)});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,8 +160,8 @@ void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
i = render_lookup_table.begin();
|
||||
|
||||
while (i != render_lookup_table.end()) {
|
||||
if (i->frame_future.isFinished()) {
|
||||
FramePtr f = i->frame_future.result();
|
||||
if (i->frame_future->IsFinished()) {
|
||||
FramePtr f = i->frame_future->Get().value<FramePtr>();
|
||||
|
||||
// Start multithreaded download here
|
||||
download_futures.append({i->hash, DownloadFrame(f, i->hash)});
|
||||
@@ -192,8 +193,8 @@ void RenderTask::Render(const TimeRangeList& range_to_cache,
|
||||
k = audio_lookup_table.begin();
|
||||
|
||||
while (k != audio_lookup_table.end()) {
|
||||
if (k->sample_future.isFinished()) {
|
||||
AudioDownloaded(k->range, k->sample_future.result());
|
||||
if (k->sample_future->IsFinished()) {
|
||||
AudioDownloaded(k->range, k->sample_future->Get().value<SampleBufferPtr>());
|
||||
|
||||
k = audio_lookup_table.erase(k);
|
||||
} else {
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
RenderTask(ViewerOutput* viewer, const VideoRenderingParams &vparams, const AudioRenderingParams &aparams);
|
||||
|
||||
protected:
|
||||
void Render(const TimeRangeList &range_to_cache, const QMatrix4x4 &mat, bool audio_enabled);
|
||||
void Render(const TimeRangeList &range_to_cache, const QMatrix4x4 &mat, bool audio_enabled, bool use_disk_cache);
|
||||
|
||||
virtual QFuture<void> DownloadFrame(FramePtr frame, const QByteArray &hash) = 0;
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
|
||||
// Start background renderer
|
||||
renderer_ = new OpenGLBackend(this);
|
||||
renderer_->SetUpdateWithGraph(true);
|
||||
|
||||
// Setup cache wait timer (waits a few seconds of inactivity before caching)
|
||||
cache_wait_timer_.setInterval(100);
|
||||
@@ -424,6 +425,11 @@ FramePtr DecodeCachedImage(const QString &fn, const rational& time)
|
||||
return frame;
|
||||
}
|
||||
|
||||
void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time)
|
||||
{
|
||||
ticket->Finish(QVariant::fromValue(DecodeCachedImage(fn, time)));
|
||||
}
|
||||
|
||||
void ViewerWidget::UpdateTextureFromNode(const rational& time)
|
||||
{
|
||||
if (!FrameExistsAtTime(time)) {
|
||||
@@ -459,16 +465,10 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time)
|
||||
}
|
||||
|
||||
// Frame was not in queue, will require rendering or decoding from cache
|
||||
QFutureWatcher<FramePtr>* watcher = new QFutureWatcher<FramePtr>();
|
||||
|
||||
connect(watcher,
|
||||
&QFutureWatcher<FramePtr>::finished,
|
||||
this,
|
||||
&ViewerWidget::RendererGeneratedFrame);
|
||||
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrame);
|
||||
nonqueue_watchers_.append(watcher);
|
||||
|
||||
watcher->setFuture(GetFrame(time, true, true));
|
||||
watcher->SetTicket(GetFrame(time, true));
|
||||
}
|
||||
|
||||
void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
@@ -636,12 +636,9 @@ void ViewerWidget::RequestNextFrameForQueue()
|
||||
|
||||
playback_queue_next_frame_ += playback_speed_;
|
||||
|
||||
QFutureWatcher<FramePtr>* watcher = new QFutureWatcher<FramePtr>();
|
||||
connect(watcher,
|
||||
&QFutureWatcher<FramePtr>::finished,
|
||||
this,
|
||||
&ViewerWidget::RendererGeneratedFrameForQueue);
|
||||
watcher->setFuture(GetFrame(next_time, false, true));
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrameForQueue);
|
||||
watcher->SetTicket(GetFrame(next_time, false));
|
||||
}
|
||||
|
||||
PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const
|
||||
@@ -649,18 +646,24 @@ PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const
|
||||
return PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline);
|
||||
}
|
||||
|
||||
QFuture<FramePtr> ViewerWidget::GetFrame(const rational &t, bool clear_render_queue, bool block_update)
|
||||
RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool clear_render_queue)
|
||||
{
|
||||
QByteArray cached_hash = GetConnectedNode()->video_frame_cache()->GetHash(t);
|
||||
if (cached_hash.isEmpty()) {
|
||||
// Frame hasn't been cached, start render job
|
||||
return renderer_->RenderFrame(t, clear_render_queue, block_update);
|
||||
if (clear_render_queue) {
|
||||
renderer_->ClearVideoQueue();
|
||||
}
|
||||
|
||||
return renderer_->RenderFrame(t);
|
||||
} else {
|
||||
// Frame has been cached, grab the frame
|
||||
QString cache_fn = GetConnectedNode()->video_frame_cache()->CachePathName(cached_hash,
|
||||
GetCurrentPixelFormat());
|
||||
|
||||
return QtConcurrent::run(DecodeCachedImage, cache_fn, t);
|
||||
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeVideo, TimeRange(t, t));
|
||||
QtConcurrent::run(DecodeCachedImage, ticket, cache_fn, t);
|
||||
return ticket;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,57 +788,45 @@ void ViewerWidget::ContextMenuScopeTriggered(QAction *action)
|
||||
|
||||
void ViewerWidget::RendererGeneratedFrame()
|
||||
{
|
||||
QFutureWatcher<FramePtr>* watcher = static_cast<QFutureWatcher<FramePtr>*>(sender());
|
||||
FramePtr frame = watcher->result();
|
||||
RenderTicketWatcher* ticket = static_cast<RenderTicketWatcher*>(sender());
|
||||
|
||||
if (nonqueue_watchers_.contains(watcher)) {
|
||||
while (!nonqueue_watchers_.isEmpty()) {
|
||||
if (nonqueue_watchers_.takeFirst() == watcher) {
|
||||
break;
|
||||
if (!ticket->WasCancelled()) {
|
||||
FramePtr frame = ticket->Get().value<FramePtr>();
|
||||
|
||||
if (nonqueue_watchers_.contains(ticket)) {
|
||||
while (!nonqueue_watchers_.isEmpty()) {
|
||||
if (nonqueue_watchers_.takeFirst() == ticket) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetDisplayImage(frame, false);
|
||||
SetDisplayImage(frame, false);
|
||||
}
|
||||
}
|
||||
|
||||
watcher->deleteLater();
|
||||
ticket->deleteLater();
|
||||
}
|
||||
|
||||
void ViewerWidget::RendererGeneratedFrameForQueue()
|
||||
{
|
||||
QFutureWatcher<FramePtr>* watcher = static_cast<QFutureWatcher<FramePtr>*>(sender());
|
||||
FramePtr frame = watcher->result();
|
||||
watcher->deleteLater();
|
||||
RenderTicketWatcher* watcher = static_cast<RenderTicketWatcher*>(sender());
|
||||
|
||||
// Ignore this signal if we've paused now
|
||||
if (IsPlaying() || prequeuing_) {
|
||||
playback_queue_.AppendTimewise({frame->timestamp(), frame}, playback_speed_);
|
||||
if (!watcher->WasCancelled()) {
|
||||
FramePtr frame = watcher->Get().value<FramePtr>();
|
||||
|
||||
foreach (ViewerWindow* window, windows_) {
|
||||
window->queue()->AppendTimewise({frame->timestamp(), frame}, playback_speed_);
|
||||
// Ignore this signal if we've paused now
|
||||
if (IsPlaying() || prequeuing_) {
|
||||
playback_queue_.AppendTimewise({frame->timestamp(), frame}, playback_speed_);
|
||||
|
||||
foreach (ViewerWindow* window, windows_) {
|
||||
window->queue()->AppendTimewise({frame->timestamp(), frame}, playback_speed_);
|
||||
}
|
||||
|
||||
if (prequeuing_ && playback_queue_.size() == kMaxPreQueueSize) {
|
||||
prequeuing_ = false;
|
||||
FinishPlayPreprocess();
|
||||
}
|
||||
}
|
||||
|
||||
if (prequeuing_ && playback_queue_.size() == kMaxPreQueueSize) {
|
||||
prequeuing_ = false;
|
||||
FinishPlayPreprocess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::HashGenerated()
|
||||
{
|
||||
QFutureWatcher<QByteArray>* watcher = static_cast<QFutureWatcher<QByteArray>*>(sender());
|
||||
|
||||
if (hash_watchers_.contains(watcher)) {
|
||||
FrameHashCache* cache = GetConnectedNode()->video_frame_cache();
|
||||
|
||||
QString cache_fn = cache->CachePathName(watcher->result(), GetCurrentPixelFormat());
|
||||
|
||||
if (QFileInfo::exists(cache_fn)) {
|
||||
cache->SetHash(hash_watchers_.value(watcher), watcher->result());
|
||||
}
|
||||
|
||||
hash_watchers_.remove(watcher);
|
||||
}
|
||||
|
||||
watcher->deleteLater();
|
||||
@@ -1211,16 +1202,6 @@ void ViewerWidget::ViewerInvalidatedRange(const TimeRange &range)
|
||||
if (!(qApp->mouseButtons() & Qt::LeftButton)) {
|
||||
cache_wait_timer_.start();
|
||||
}
|
||||
|
||||
/*
|
||||
QList<rational> invalidated_frames = GetConnectedNode()->video_frame_cache()->GetFrameListFromTimeRange({range});
|
||||
foreach (const rational& r, invalidated_frames) {
|
||||
QFutureWatcher<QByteArray>* watcher = new QFutureWatcher<QByteArray>();
|
||||
connect(watcher, &QFutureWatcher<QByteArray>::finished, this, &ViewerWidget::HashGenerated);
|
||||
hash_watchers_.insert(watcher, r);
|
||||
watcher->setFuture(renderer_->Hash(r, true));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "panel/scope/scope.h"
|
||||
#include "render/backend/opengl/openglbackend.h"
|
||||
#include "render/backend/renderticketwatcher.h"
|
||||
#include "task/cache/cache.h"
|
||||
#include "viewerdisplay.h"
|
||||
#include "viewerplaybacktimer.h"
|
||||
@@ -194,7 +195,7 @@ private:
|
||||
|
||||
PixelFormat::Format GetCurrentPixelFormat() const;
|
||||
|
||||
QFuture<FramePtr> GetFrame(const rational& t, bool clear_render_queue, bool block_update);
|
||||
RenderTicketPtr GetFrame(const rational& t, bool clear_render_queue);
|
||||
|
||||
void FinishPlayPreprocess();
|
||||
|
||||
@@ -240,9 +241,7 @@ private:
|
||||
|
||||
bool prequeuing_;
|
||||
|
||||
QList< QFutureWatcher<FramePtr>* > nonqueue_watchers_;
|
||||
|
||||
QHash<QFutureWatcher<QByteArray>*, rational> hash_watchers_;
|
||||
QList<RenderTicketWatcher*> nonqueue_watchers_;
|
||||
|
||||
QTimer cache_wait_timer_;
|
||||
|
||||
@@ -289,8 +288,6 @@ private slots:
|
||||
|
||||
void RendererGeneratedFrameForQueue();
|
||||
|
||||
void HashGenerated();
|
||||
|
||||
void StartBackgroundCaching();
|
||||
|
||||
void BackgroundCacheFinished(Task *t);
|
||||
|
||||
Reference in New Issue
Block a user