diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 9da151b7c..0eebf8055 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -335,7 +335,7 @@ void Decoder::UpdateLastAccessed() uint qHash(Decoder::CodecStream stream, uint seed) { - return qHash(stream.filename(), seed) ^ qHash(stream.stream(), seed); + return qHash(stream.filename(), seed) ^ qHash(stream.stream(), seed) ^ qHash(stream.block(), seed); } } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 061378be0..99fa74428 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -34,6 +34,7 @@ extern "C" { #include "codec/frame.h" #include "codec/samplebuffer.h" #include "common/rational.h" +#include "node/block/block.h" #include "node/project/footage/footage.h" #include "node/project/footage/footagedescription.h" #include "task/task.h" @@ -85,13 +86,15 @@ public: { public: CodecStream() : - stream_(-1) + stream_(-1), + block_(nullptr) { } - CodecStream(const QString& filename, int stream) : + CodecStream(const QString& filename, int stream, Block *block) : filename_(filename), - stream_(stream) + stream_(stream), + block_(block) { } @@ -125,11 +128,18 @@ public: return stream_; } + Block *block() const + { + return block_; + } + private: QString filename_; int stream_; + Block *block_; + }; /** diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 96d69df9b..b963306ff 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -62,7 +62,6 @@ FFmpegDecoder::FFmpegDecoder() : native_output_pix_fmt_(VideoParams::kFormatInvalid), working_frame_(nullptr), working_packet_(nullptr), - is_working_(false), cache_at_zero_(false), cache_at_eof_(false) { @@ -749,7 +748,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt * int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); const int64_t min_seek = -instance_.avstream()->start_time; - int64_t seek_ts = target_ts; + int64_t seek_ts = std::max(min_seek, target_ts - MaximumQueueSize()); bool still_seeking = false; if (time != kAnyTimecode) { @@ -838,7 +837,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt * } else { // Cut down to thread count - 1 before we acquire a new frame - if (cached_frames_.size() == size_t(QThread::idealThreadCount())) { + if (cached_frames_.size() == size_t(MaximumQueueSize())) { RemoveFirstFrame(); } @@ -1039,6 +1038,11 @@ void FFmpegDecoder::RemoveFirstFrame() cache_at_zero_ = false; } +int FFmpegDecoder::MaximumQueueSize() +{ + return QThread::idealThreadCount(); +} + FFmpegDecoder::Instance::Instance() : fmt_ctx_(nullptr), codec_ctx_(nullptr), diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 80c46fb1f..4e51f496f 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -155,6 +155,8 @@ private: void RemoveFirstFrame(); + static int MaximumQueueSize(); + RetrieveVideoParams filter_params_; AVFilterGraph* filter_graph_; AVFilterContext* buffersrc_ctx_; @@ -171,9 +173,6 @@ private: std::list cached_frames_; - bool is_working_; - QMutex is_working_mutex_; - bool cache_at_zero_; bool cache_at_eof_; diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index da04f5e99..ca4267d52 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -350,7 +350,9 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR NodeValueTable table; if (active_block) { + block_stack_.push_back(active_block); table = GenerateTable(active_block, Track::TransformRangeForBlock(active_block, range), track); + block_stack_.pop_back(); } return table; diff --git a/app/node/traverser.h b/app/node/traverser.h index fca5083f3..105955450 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -146,6 +146,11 @@ protected: void ResolveJobs(NodeValue &value, const TimeRange &range); + Block *GetCurrentBlock() const + { + return block_stack_.empty() ? nullptr : block_stack_.back(); + } + private: void PreProcessRow(const TimeRange &range, NodeValueRow &row); @@ -162,6 +167,8 @@ private: const Node *transform_now_; QTransform *transform_; + std::list block_stack_; + }; } diff --git a/app/render/rendercache.h b/app/render/rendercache.h index 114244fb8..77a56780d 100644 --- a/app/render/rendercache.h +++ b/app/render/rendercache.h @@ -39,15 +39,10 @@ private: }; -struct DecoderPair { - DecoderPair() - { - decoder = nullptr; - last_modified = 0; - } - - DecoderPtr decoder; - qint64 last_modified; +struct DecoderPair +{ + DecoderPtr decoder = nullptr; + qint64 last_modified = 0; }; using DecoderCache = RenderCache; diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index 80683c285..b66fd6ca8 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -72,8 +72,21 @@ TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const V return output; } +QVariant Renderer::GetDefaultShader() +{ + if (default_shader_.isNull()) { + default_shader_ = CreateNativeShader(ShaderCode(QString(), QString())); + } + + return default_shader_; +} + void Renderer::Destroy() { + if (!default_shader_.isNull()) { + DestroyNativeShader(default_shader_); + } + color_cache_.clear(); if (!interlace_texture_.isNull()) { diff --git a/app/render/renderer.h b/app/render/renderer.h index 749d777cf..839be32cf 100644 --- a/app/render/renderer.h +++ b/app/render/renderer.h @@ -76,6 +76,8 @@ public: TexturePtr InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms); + QVariant GetDefaultShader(); + void Destroy(); virtual void PostDestroy() = 0; @@ -134,6 +136,8 @@ private: QMutex color_cache_mutex_; + QVariant default_shader_; + QVariant interlace_texture_; }; diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index b86cc64ea..56ea08345 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -54,7 +54,6 @@ RenderManager::RenderManager(QObject *parent) : decoder_cache_ = new DecoderCache(); shader_cache_ = new ShaderCache(); - default_shader_ = context_->CreateNativeShader(ShaderCode(QString(), QString())); } else { qCritical() << "Tried to initialize unknown graphics backend"; context_ = nullptr; @@ -65,8 +64,6 @@ RenderManager::RenderManager(QObject *parent) : RenderManager::~RenderManager() { if (context_) { - context_->DestroyNativeShader(default_shader_); - delete shader_cache_; delete decoder_cache_; @@ -130,7 +127,7 @@ void RenderManager::RunTicket(RenderTicketPtr ticket) const return; } - RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_, default_shader_); + RenderProcessor::Process(ticket, context_, decoder_cache_, shader_cache_); } } diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 6bbe3ba17..1059ecd9e 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -183,8 +183,6 @@ private: ShaderCache* shader_cache_; - QVariant default_shader_; - }; } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 5a90b9e89..e89f3bbc6 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -35,12 +35,11 @@ namespace olive { #define super NodeTraverser -RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache* decoder_cache, ShaderCache *shader_cache, QVariant default_shader) : +RenderProcessor::RenderProcessor(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache* decoder_cache, ShaderCache *shader_cache) : ticket_(ticket), render_ctx_(render_ctx), decoder_cache_(decoder_cache), - shader_cache_(shader_cache), - default_shader_(default_shader) + shader_cache_(shader_cache) { } @@ -116,7 +115,7 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time job.Insert(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture))); job.Insert(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, matrix)); - render_ctx_->BlitToTexture(default_shader_, job, blit_tex.get()); + render_ctx_->BlitToTexture(render_ctx_->GetDefaultShader(), job, blit_tex.get()); // Replace texture that we're going to download in the next step texture = blit_tex; @@ -258,10 +257,10 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c // No decoder decoder.decoder = Decoder::CreateFromID(decoder_id); decoder.last_modified = file_last_modified; + decoder_cache_->insert(stream, decoder); + locker.unlock(); - if (decoder.decoder->Open(stream)) { - decoder_cache_->insert(stream, decoder); - } else { + if (!decoder.decoder->Open(stream)) { qWarning() << "Failed to open decoder for" << stream.filename() << "::" << stream.stream(); return nullptr; @@ -271,9 +270,9 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, c return decoder.decoder; } -void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache *decoder_cache, ShaderCache *shader_cache, QVariant default_shader) +void RenderProcessor::Process(RenderTicketPtr ticket, Renderer *render_ctx, DecoderCache *decoder_cache, ShaderCache *shader_cache) { - RenderProcessor p(ticket, render_ctx, decoder_cache, shader_cache, default_shader); + RenderProcessor p(ticket, render_ctx, decoder_cache, shader_cache); p.Run(); } @@ -404,7 +403,7 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ qWarning() << "HAVEN'T GOTTEN DEFAULT INPUT COLORSPACE"; } - Decoder::CodecStream default_codec_stream(stream.filename(), stream_data.stream_index()); + Decoder::CodecStream default_codec_stream(stream.filename(), stream_data.stream_index(), GetCurrentBlock()); QString decoder_id = stream.decoder(); @@ -426,7 +425,7 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); // Decoder will close automatically since it's a stream_ptr - decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index())); + decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index(), GetCurrentBlock())); break; } } @@ -472,7 +471,7 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ void RenderProcessor::ProcessAudioFootage(SampleBuffer &destination, const FootageJob &stream, const TimeRange &input_time) { - DecoderPtr decoder = ResolveDecoderFromInput(stream.decoder(), Decoder::CodecStream(stream.filename(), stream.audio_params().stream_index())); + DecoderPtr decoder = ResolveDecoderFromInput(stream.decoder(), Decoder::CodecStream(stream.filename(), stream.audio_params().stream_index(), nullptr)); if (decoder) { const AudioParams& audio_params = GetCacheAudioParams(); diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 9c0ad40cd..db147bd3e 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -32,7 +32,7 @@ namespace olive { class RenderProcessor : public NodeTraverser { public: - static void Process(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader); + static void Process(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache); struct RenderedWaveform { const ClipBlock* block; @@ -69,7 +69,7 @@ protected: virtual void ConvertToReferenceSpace(TexturePtr destination, TexturePtr source, const QString &input_cs) override; private: - RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache, QVariant default_shader); + RenderProcessor(RenderTicketPtr ticket, Renderer* render_ctx, DecoderCache* decoder_cache, ShaderCache* shader_cache); TexturePtr GenerateTexture(const rational& time, const rational& frame_length); @@ -87,8 +87,6 @@ private: ShaderCache* shader_cache_; - QVariant default_shader_; - }; } diff --git a/app/threading/threadpool.cpp b/app/threading/threadpool.cpp index b048f3519..8b33d72db 100644 --- a/app/threading/threadpool.cpp +++ b/app/threading/threadpool.cpp @@ -29,52 +29,67 @@ ThreadPool::ThreadPool(unsigned threads, QObject *parent) : threads = std::thread::hardware_concurrency(); } + available_count_ = threads; for (unsigned i = 0; i < threads; i += 1) { - worker_threads_.emplace_back(std::bind(&ThreadPool::thread_exec, this)); + worker_threads_.emplace_back(std::bind(&ThreadPool::thread_exec, this, &tasks_, &task_mutex_, &cond_)); } + + // Make single reserved thread for high priority tasks (usually audio) so they don't get stuck + // behind a lot of slow tasks + high_thread_ = std::thread(std::thread(std::bind(&ThreadPool::thread_exec, this, &high_tasks_, &high_mutex_, &high_cond_))); } void ThreadPool::AddTicket(RenderTicketPtr ticket, RenderTicketPriority priority) { - std::lock_guard lock(task_mutex_); - if (priority == RenderTicketPriority::kHigh) { - tasks_.emplace_front(std::move(ticket)); + std::lock_guard lock(high_mutex_); + high_tasks_.emplace_back(std::move(ticket)); + high_cond_.notify_one(); } else { + std::lock_guard lock(task_mutex_); tasks_.emplace_back(std::move(ticket)); + cond_.notify_one(); } - - cond_.notify_one(); } bool ThreadPool::RemoveTicket(RenderTicketPtr ticket) { - std::lock_guard lock(task_mutex_); - - const auto it = std::find(tasks_.begin(), tasks_.end(), ticket); - if (it == tasks_.end()) { - return false; + { + std::lock_guard lock(task_mutex_); + const auto it = std::find(tasks_.begin(), tasks_.end(), ticket); + if (it != tasks_.end()) { + tasks_.erase(it); + return true; + } } - tasks_.erase(it); - return true; + { + std::lock_guard lock(high_mutex_); + const auto it = std::find(high_tasks_.begin(), high_tasks_.end(), ticket); + if (it != high_tasks_.end()) { + high_tasks_.erase(it); + return true; + } + } + + return false; } -void ThreadPool::thread_exec() +void ThreadPool::thread_exec(std::deque *queue, std::mutex *mutex, std::condition_variable *cond) { while (true) { TaskType task; { - std::unique_lock lock(task_mutex_); - cond_.wait(lock, [this]{ return this->end_threadp_ || !this->tasks_.empty(); }); + std::unique_lock lock(*mutex); + cond->wait(lock, [this, queue]{ return this->end_threadp_ || !queue->empty(); }); - if (this->end_threadp_ && this->tasks_.empty()) { + if (this->end_threadp_ && queue->empty()) { break; } - task = std::move(tasks_.front()); - tasks_.pop_front(); + task = std::move(queue->front()); + queue->pop_front(); } RunTicket(task); @@ -85,10 +100,12 @@ ThreadPool::~ThreadPool() { end_threadp_ = true; cond_.notify_all(); + high_cond_.notify_all(); for (auto &e : worker_threads_) { e.join(); } + high_thread_.join(); } } diff --git a/app/threading/threadpool.h b/app/threading/threadpool.h index 3c3204bb1..3c1143f55 100644 --- a/app/threading/threadpool.h +++ b/app/threading/threadpool.h @@ -49,13 +49,20 @@ public: virtual ~ThreadPool() override; private: - void thread_exec(); + void thread_exec(std::deque *queue, std::mutex *mutex, std::condition_variable *cond); std::vector worker_threads_; std::deque tasks_; std::mutex task_mutex_; std::condition_variable cond_; + + std::thread high_thread_; + std::deque high_tasks_; + std::mutex high_mutex_; + std::condition_variable high_cond_; + std::atomic_bool end_threadp_{false}; + std::atomic_int available_count_; }; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 89dbd4d37..3f83d5ae7 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -702,7 +702,10 @@ void ViewerWidget::UpdateTextureFromNode() connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrame); nonqueue_watchers_.append(watcher); - watcher->SetTicket(GetFrame(time, RenderTicketPriority::kHigh)); + // Clear queue because we want this frame more than any others + auto_cacher_->ClearSingleFrameRenders(); + + watcher->SetTicket(GetFrame(time, RenderTicketPriority::kNormal)); } else { // There is definitely no frame here, we can immediately flip to showing nothing nonqueue_watchers_.clear(); @@ -753,7 +756,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) playback_speed_ = speed; play_in_to_out_only_ = in_to_out_only; - playback_queue_next_frame_ = GetTimestamp(); + playback_queue_next_frame_ = GetTimestamp() + playback_speed_; controls_->ShowPauseButton(); @@ -767,18 +770,10 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) prequeuing_video_ = true; prequeue_count_ = 0; - // We "prioritize" the frames, which means they're pushed to the top of the render queue, - // we queue in reverse so that they're still queued in order - - playback_queue_next_frame_ += playback_speed_ * prequeue_length_; - int64_t temp = playback_queue_next_frame_; - for (int i=0; iqueue()->size(); i