fixed various segfaults in decoder and renderer
This commit is contained in:
@@ -1463,7 +1463,9 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in
|
||||
// Open codec
|
||||
error_code = avcodec_open2(codec_ctx_, codec, &opts_);
|
||||
if (error_code < 0) {
|
||||
qDebug() << "Failed to open codec" << codec->id << error_code;
|
||||
char buf[50];
|
||||
av_strerror(error_code, buf, 50);
|
||||
qDebug() << "Failed to open codec" << codec->id << error_code << buf;
|
||||
ClearResources();
|
||||
return;
|
||||
}
|
||||
|
||||
+32
-11
@@ -57,6 +57,7 @@ public:
|
||||
*/
|
||||
MemoryPool(int element_count) {
|
||||
element_count_ = element_count;
|
||||
ignore_arena_empty_signal_ = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,6 +66,7 @@ public:
|
||||
* Deletes all arenas.
|
||||
*/
|
||||
virtual ~MemoryPool() {
|
||||
ignore_arena_empty_signal_ = true;
|
||||
qDeleteAll(arenas_);
|
||||
}
|
||||
|
||||
@@ -112,7 +114,7 @@ public:
|
||||
* Automatically releases this element's memory back to the arena it was retrieved from.
|
||||
*/
|
||||
~Element() {
|
||||
parent_->Release(this);
|
||||
release();
|
||||
}
|
||||
|
||||
DISABLE_COPY_MOVE(Element)
|
||||
@@ -151,6 +153,13 @@ public:
|
||||
return accessed_;
|
||||
}
|
||||
|
||||
void release() {
|
||||
if (data_) {
|
||||
parent_->Release(this);
|
||||
data_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Arena* parent_;
|
||||
|
||||
@@ -176,11 +185,13 @@ public:
|
||||
Arena(MemoryPool* parent) {
|
||||
parent_ = parent;
|
||||
data_ = nullptr;
|
||||
use_count_ = 0;
|
||||
}
|
||||
|
||||
~Arena() {
|
||||
// FIXME: Invalidate elements that have been lent out?
|
||||
QLinkedList<Element*> copy = lent_elements_;
|
||||
foreach (Element* e, copy) {
|
||||
e->release();
|
||||
}
|
||||
|
||||
delete [] data_;
|
||||
}
|
||||
@@ -197,8 +208,11 @@ public:
|
||||
if (available_.at(i)) {
|
||||
// This buffer is available
|
||||
available_.replace(i, false);
|
||||
use_count_++;
|
||||
return std::make_shared<Element>(this, reinterpret_cast<T*>(data_ + i * element_sz_));
|
||||
|
||||
ElementPtr e = std::make_shared<Element>(this,
|
||||
reinterpret_cast<T*>(data_ + i * element_sz_));
|
||||
lent_elements_.append(e.get());
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,17 +229,18 @@ public:
|
||||
int index = diff / element_sz_;
|
||||
|
||||
available_.replace(index, true);
|
||||
use_count_--;
|
||||
|
||||
if (!use_count_) {
|
||||
lent_elements_.removeOne(e);
|
||||
|
||||
if (lent_elements_.isEmpty()) {
|
||||
locker.unlock();
|
||||
parent_->ArenaIsEmpty(this);
|
||||
}
|
||||
}
|
||||
|
||||
const int& GetUsageCount() {
|
||||
int GetUsageCount() {
|
||||
QMutexLocker locker(&lock_);
|
||||
return use_count_;
|
||||
return lent_elements_.size();
|
||||
}
|
||||
|
||||
bool Allocate(size_t ele_sz, size_t nb_elements) {
|
||||
@@ -242,7 +257,6 @@ public:
|
||||
return true;
|
||||
} else {
|
||||
available_.clear();
|
||||
data_ = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -267,7 +281,7 @@ public:
|
||||
|
||||
size_t element_sz_;
|
||||
|
||||
int use_count_;
|
||||
QLinkedList<Element*> lent_elements_;
|
||||
|
||||
};
|
||||
|
||||
@@ -317,6 +331,11 @@ public:
|
||||
}
|
||||
|
||||
void ArenaIsEmpty(Arena* a) {
|
||||
// FIXME: Does this need to be mutexed?
|
||||
if (ignore_arena_empty_signal_) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
if (!a->GetUsageCount()) {
|
||||
@@ -343,6 +362,8 @@ private:
|
||||
|
||||
QMutex lock_;
|
||||
|
||||
bool ignore_arena_empty_signal_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -36,8 +36,9 @@ OLIVE_NAMESPACE_ENTER
|
||||
/**
|
||||
* @brief Retrieves the width of a string according to certain QFontMetrics
|
||||
*
|
||||
* QFontMetrics::width() has been deprecatd in favor of QFontMetrics::horizontalAdvance(), but the latter was only
|
||||
* introduced in 5.11+. This function wraps the latter for 5.11+ and the former for earlier.
|
||||
* QFontMetrics::width() has been deprecatd in favor of QFontMetrics::horizontalAdvance(), but the
|
||||
* latter was only introduced in 5.11+. This function wraps the latter for 5.11+ and the former for
|
||||
* earlier.
|
||||
*/
|
||||
int QFontMetricsWidth(QFontMetrics fm, const QString& s);
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ TrackList::TrackList(ViewerOutput *parent, const Timeline::TrackType &type, Node
|
||||
track_input_(track_input),
|
||||
type_(type)
|
||||
{
|
||||
connect(track_input, &NodeInputArray::SubParamEdgeAdded, this, &TrackList::TrackConnected);
|
||||
connect(track_input, &NodeInputArray::SubParamEdgeRemoved, this, &TrackList::TrackDisconnected);
|
||||
connect(track_input_, &NodeInputArray::SubParamEdgeAdded, this, &TrackList::TrackConnected);
|
||||
connect(track_input_, &NodeInputArray::SubParamEdgeRemoved, this, &TrackList::TrackDisconnected);
|
||||
}
|
||||
|
||||
const Timeline::TrackType &TrackList::type() const
|
||||
@@ -192,6 +192,7 @@ void TrackList::TrackConnected(NodeEdgePtr edge)
|
||||
connect(connected_track, &TrackOutput::TrackLengthChanged, this, &TrackList::UpdateTotalLength);
|
||||
connect(connected_track, &TrackOutput::TrackHeightChanged, this, &TrackList::TrackHeightChangedSlot);
|
||||
|
||||
qDebug() << "Setting track type" << connected_track << type_;
|
||||
connected_track->set_track_type(type_);
|
||||
|
||||
emit TrackListChanged();
|
||||
|
||||
@@ -30,7 +30,6 @@ set(OLIVE_SOURCES
|
||||
render/backend/renderworker.h
|
||||
render/backend/renderworker.cpp
|
||||
|
||||
render/backend/rendercache.h
|
||||
render/backend/colorprocessorcache.h
|
||||
render/backend/decodercache.h
|
||||
|
||||
|
||||
@@ -23,11 +23,10 @@
|
||||
|
||||
#include "project/item/footage/stream.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "rendercache.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
using ColorProcessorCache = RenderCache<QString, ColorProcessorPtr>;
|
||||
using ColorProcessorCache = QHash<QString, ColorProcessorPtr>;
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
|
||||
@@ -23,21 +23,10 @@
|
||||
|
||||
#include "codec/decoder.h"
|
||||
#include "project/item/footage/stream.h"
|
||||
#include "rendercache.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class DecoderCache : public RenderCache<Stream*, DecoderPtr>
|
||||
{
|
||||
public:
|
||||
DecoderCache() = default;
|
||||
|
||||
QMutex* lock() {return &lock_;}
|
||||
|
||||
private:
|
||||
QMutex lock_;
|
||||
|
||||
};
|
||||
using DecoderCache = QHash<Stream*, DecoderPtr>;
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ RenderWorker *OpenGLBackend::CreateNewWorker()
|
||||
}
|
||||
}
|
||||
|
||||
return new OpenGLWorker(proxy_);
|
||||
return new OpenGLWorker(this, proxy_);
|
||||
}
|
||||
|
||||
void OpenGLBackend::ClearProxy()
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#define OPENGLCOLORPROCESSOR_H
|
||||
|
||||
#include "openglshader.h"
|
||||
#include "render/backend/rendercache.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
@@ -63,7 +62,7 @@ private slots:
|
||||
|
||||
};
|
||||
|
||||
using OpenGLColorProcessorCache = RenderCache<QString, OpenGLColorProcessorPtr>;
|
||||
using OpenGLColorProcessorCache = QHash<QString, OpenGLColorProcessorPtr>;
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
|
||||
@@ -74,13 +74,13 @@ NodeValue OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, const Vide
|
||||
// Set up OCIO context
|
||||
QString colorspace_match = video_stream->get_colorspace_match_string();
|
||||
|
||||
OpenGLColorProcessorPtr color_processor = std::static_pointer_cast<OpenGLColorProcessor>(color_cache_.Get(colorspace_match));
|
||||
OpenGLColorProcessorPtr color_processor = std::static_pointer_cast<OpenGLColorProcessor>(color_cache_.value(colorspace_match));
|
||||
|
||||
if (!color_processor) {
|
||||
color_processor = OpenGLColorProcessor::Create(video_stream->footage()->project()->color_manager(),
|
||||
video_stream->colorspace(),
|
||||
video_stream->footage()->project()->color_manager()->GetReferenceColorSpace());
|
||||
color_cache_.Add(colorspace_match, color_processor);
|
||||
color_cache_.insert(colorspace_match, color_processor);
|
||||
}
|
||||
|
||||
ColorManager::OCIOMethod ocio_method = ColorManager::GetOCIOMethodForMode(params.mode());
|
||||
@@ -172,7 +172,7 @@ NodeValue OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, const Vide
|
||||
|
||||
void OpenGLProxy::Close()
|
||||
{
|
||||
shader_cache_.Clear();
|
||||
shader_cache_.clear();
|
||||
buffer_.Destroy();
|
||||
copy_pipeline_ = nullptr;
|
||||
functions_ = nullptr;
|
||||
@@ -186,7 +186,7 @@ void OpenGLProxy::RunNodeAccelerated(const Node *node,
|
||||
NodeValueTable &output_params,
|
||||
const VideoRenderingParams& params)
|
||||
{
|
||||
OpenGLShaderPtr shader = shader_cache_.Get(node->ShaderID(input_params));
|
||||
OpenGLShaderPtr shader = shader_cache_.value(node->ShaderID(input_params));
|
||||
|
||||
if (!shader) {
|
||||
// Since we have shader code, compile it now
|
||||
@@ -208,7 +208,7 @@ void OpenGLProxy::RunNodeAccelerated(const Node *node,
|
||||
shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_code);
|
||||
shader->link();
|
||||
|
||||
shader_cache_.Add(node->id(), shader);
|
||||
shader_cache_.insert(node->id(), shader);
|
||||
}
|
||||
|
||||
// Create the output textures
|
||||
|
||||
@@ -24,11 +24,10 @@
|
||||
#include <QString>
|
||||
|
||||
#include "openglshader.h"
|
||||
#include "render/backend/rendercache.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
using OpenGLShaderCache = RenderCache<QString, OpenGLShaderPtr>;
|
||||
using OpenGLShaderCache = QHash<QString, OpenGLShaderPtr>;
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
OpenGLWorker::OpenGLWorker(OpenGLProxy* proxy) :
|
||||
OpenGLWorker::OpenGLWorker(RenderBackend *parent, OpenGLProxy* proxy) :
|
||||
RenderWorker(parent),
|
||||
proxy_(proxy)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ OLIVE_NAMESPACE_ENTER
|
||||
class OpenGLWorker : public RenderWorker
|
||||
{
|
||||
public:
|
||||
OpenGLWorker(OpenGLProxy* proxy);
|
||||
OpenGLWorker(RenderBackend* parent, OpenGLProxy* proxy);
|
||||
|
||||
protected:
|
||||
virtual void TextureToFrame(const QVariant& texture, FramePtr frame, const QMatrix4x4 &mat) const override;
|
||||
|
||||
@@ -32,6 +32,8 @@ OLIVE_NAMESPACE_ENTER
|
||||
RenderBackend::RenderBackend(QObject *parent) :
|
||||
QObject(parent),
|
||||
viewer_node_(nullptr),
|
||||
video_instance_queuer_(0),
|
||||
audio_instance_queuer_(0),
|
||||
divider_(1),
|
||||
render_mode_(RenderMode::kOnline),
|
||||
pix_fmt_(PixelFormat::PIX_FMT_RGBA32F),
|
||||
@@ -57,7 +59,7 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
|
||||
CancelQueue();
|
||||
|
||||
// Delete all of our copied nodes
|
||||
foreach (RenderWorker* instance, instance_pool_) {
|
||||
foreach (RenderWorker* instance, video_instance_pool_) {
|
||||
instance->Close();
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
|
||||
disconnect(viewer_node_->audio_playback_cache(),
|
||||
&AudioPlaybackCache::Invalidated,
|
||||
this,
|
||||
&RenderBackend::AudioCallback);
|
||||
&RenderBackend::AudioInvalidated);
|
||||
}
|
||||
|
||||
// Set viewer node
|
||||
@@ -77,7 +79,7 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
|
||||
|
||||
if (viewer_node_) {
|
||||
// Initiate instances with new node
|
||||
foreach (RenderWorker* instance, instance_pool_) {
|
||||
foreach (RenderWorker* instance, video_instance_pool_) {
|
||||
instance->Init(viewer_node_);
|
||||
}
|
||||
|
||||
@@ -89,34 +91,36 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
|
||||
connect(viewer_node_->audio_playback_cache(),
|
||||
&AudioPlaybackCache::Invalidated,
|
||||
this,
|
||||
&RenderBackend::AudioCallback);
|
||||
&RenderBackend::AudioInvalidated);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::CancelQueue()
|
||||
{
|
||||
// FIXME: Implement something better than this...
|
||||
thread_pool_.waitForDone();
|
||||
video_thread_pool_.waitForDone();
|
||||
}
|
||||
|
||||
QFuture<QByteArray> RenderBackend::Hash(const rational &time)
|
||||
QFuture<QByteArray> RenderBackend::Hash(const rational &time, bool block_for_update)
|
||||
{
|
||||
return QtConcurrent::run(&thread_pool_,
|
||||
GetInstanceFromPool(),
|
||||
return QtConcurrent::run(&video_thread_pool_,
|
||||
GetInstanceFromPool(video_instance_pool_, video_thread_pool_, video_instance_queuer_),
|
||||
&RenderWorker::Hash,
|
||||
time);
|
||||
time,
|
||||
block_for_update);
|
||||
}
|
||||
|
||||
QFuture<FramePtr> RenderBackend::RenderFrame(const rational &time, bool clear_queue)
|
||||
QFuture<FramePtr> RenderBackend::RenderFrame(const rational &time, bool clear_queue, bool block_for_update)
|
||||
{
|
||||
if (clear_queue) {
|
||||
thread_pool_.clear();
|
||||
video_thread_pool_.clear();
|
||||
}
|
||||
|
||||
return QtConcurrent::run(&thread_pool_,
|
||||
GetInstanceFromPool(),
|
||||
return QtConcurrent::run(&video_thread_pool_,
|
||||
GetInstanceFromPool(video_instance_pool_, video_thread_pool_, video_instance_queuer_),
|
||||
&RenderWorker::RenderFrame,
|
||||
time);
|
||||
time,
|
||||
block_for_update);
|
||||
}
|
||||
|
||||
void RenderBackend::SetDivider(const int ÷r)
|
||||
@@ -146,19 +150,26 @@ void RenderBackend::SetVideoDownloadMatrix(const QMatrix4x4 &mat)
|
||||
|
||||
void RenderBackend::NodeGraphChanged(NodeInput *source)
|
||||
{
|
||||
QLinkedList<RenderWorker*>::iterator i;
|
||||
|
||||
for (i=instance_pool_.begin(); i!=instance_pool_.end(); i++) {
|
||||
(*i)->Queue(source);
|
||||
foreach (RenderWorker* worker, video_instance_pool_) {
|
||||
worker->Queue(source);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::UpdateInstance(RenderWorker *instance)
|
||||
{
|
||||
instance->SetAvailable(false);
|
||||
instance->ProcessQueue();
|
||||
instance->SetVideoParams(video_params());
|
||||
instance->SetAudioParams(audio_params());
|
||||
instance->SetVideoDownloadMatrix(video_download_matrix_);
|
||||
}
|
||||
|
||||
void RenderBackend::Close()
|
||||
{
|
||||
CancelQueue();
|
||||
|
||||
qDeleteAll(instance_pool_);
|
||||
instance_pool_.clear();
|
||||
qDeleteAll(video_instance_pool_);
|
||||
video_instance_pool_.clear();
|
||||
}
|
||||
|
||||
VideoRenderingParams RenderBackend::video_params() const
|
||||
@@ -171,60 +182,63 @@ AudioRenderingParams RenderBackend::audio_params() const
|
||||
return AudioRenderingParams(viewer_node_->audio_params(), sample_fmt_);
|
||||
}
|
||||
|
||||
RenderWorker *RenderBackend::GetInstanceFromPool()
|
||||
RenderWorker *RenderBackend::GetInstanceFromPool(QVector<RenderWorker *> &worker_pool, QThreadPool &thread_pool, int &instance_queuer)
|
||||
{
|
||||
RenderWorker* instance = nullptr;
|
||||
|
||||
QLinkedList<RenderWorker*>::iterator i;
|
||||
|
||||
for (i=instance_pool_.begin(); i!=instance_pool_.end(); i++) {
|
||||
if ((*i)->IsAvailable()) {
|
||||
instance = *i;
|
||||
foreach (RenderWorker* worker, worker_pool) {
|
||||
if (worker->IsAvailable()) {
|
||||
instance = worker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!instance) {
|
||||
instance = CreateNewWorker();
|
||||
instance_pool_.append(instance);
|
||||
if (worker_pool.size() < thread_pool.maxThreadCount()) {
|
||||
// Can create another instance
|
||||
instance = CreateNewWorker();
|
||||
worker_pool.append(instance);
|
||||
|
||||
if (viewer_node_) {
|
||||
instance->Init(viewer_node_);
|
||||
if (viewer_node_) {
|
||||
instance->Init(viewer_node_);
|
||||
}
|
||||
|
||||
connect(instance,
|
||||
&RenderWorker::FinishedJob,
|
||||
this,
|
||||
&RenderBackend::WorkerFinished,
|
||||
Qt::QueuedConnection);
|
||||
} else {
|
||||
instance = worker_pool.at(instance_queuer % worker_pool.size());
|
||||
instance_queuer++;
|
||||
}
|
||||
|
||||
connect(instance,
|
||||
&RenderWorker::FinishedJob,
|
||||
this,
|
||||
&RenderBackend::WorkerFinished,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
instance->SetAvailable(false);
|
||||
instance->ProcessQueue();
|
||||
instance->SetVideoParams(video_params());
|
||||
instance->SetAudioParams(audio_params());
|
||||
instance->SetVideoDownloadMatrix(video_download_matrix_);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void RenderBackend::AudioCallback()
|
||||
void RenderBackend::AudioInvalidated(const TimeRange& r)
|
||||
{
|
||||
qDebug() << "STUB";
|
||||
/*
|
||||
AudioPlaybackCache* pb_cache = viewer_node_->audio_playback_cache();
|
||||
QThreadPool* thread_pool = audio_copy_map_.thread_pool();
|
||||
QFutureWatcher<SampleBufferPtr>* watcher = new QFutureWatcher<SampleBufferPtr>();
|
||||
connect(watcher, &QFutureWatcher<SampleBufferPtr>::finished, this, &RenderBackend::AudioRendered);
|
||||
|
||||
while (!pb_cache->IsFullyValidated()
|
||||
&& thread_pool->activeThreadCount() < thread_pool->maxThreadCount()) {
|
||||
// FIXME: Trigger a background audio thread
|
||||
TimeRange r = viewer_node_->audio_playback_cache()->GetInvalidatedRanges().first();
|
||||
watcher->setFuture(QtConcurrent::run(&video_thread_pool_,
|
||||
GetInstanceFromPool(audio_instance_pool_, audio_thread_pool_, audio_instance_queuer_),
|
||||
&RenderWorker::RenderAudio,
|
||||
r));
|
||||
}
|
||||
|
||||
qDebug() << "FIXME: Start rendering audio at:" << r;
|
||||
void RenderBackend::AudioRendered()
|
||||
{
|
||||
QFutureWatcher<SampleBufferPtr>* watcher = static_cast<QFutureWatcher<SampleBufferPtr>*>(sender());
|
||||
|
||||
break;
|
||||
if (watcher->result()) {
|
||||
qDebug() << "AUDIO: Received" << watcher->result()->sample_count_per_channel() << "samples!";
|
||||
} else {
|
||||
qDebug() << "AUDIO: Received null";
|
||||
}
|
||||
*/
|
||||
|
||||
watcher->deleteLater();
|
||||
}
|
||||
|
||||
void RenderBackend::WorkerFinished()
|
||||
|
||||
@@ -47,12 +47,12 @@ public:
|
||||
/**
|
||||
* @brief Asynchronously generate a hash at a given time
|
||||
*/
|
||||
QFuture<QByteArray> Hash(const rational& time);
|
||||
QFuture<QByteArray> Hash(const rational& time, bool block_for_update);
|
||||
|
||||
/**
|
||||
* @brief Asynchronously generate a frame at a given time
|
||||
*/
|
||||
QFuture<FramePtr> RenderFrame(const rational& time, bool clear_queue);
|
||||
QFuture<FramePtr> RenderFrame(const rational& time, bool clear_queue, bool block_for_update);
|
||||
|
||||
void SetDivider(const int& divider);
|
||||
|
||||
@@ -67,6 +67,8 @@ public:
|
||||
public slots:
|
||||
void NodeGraphChanged(NodeInput *source);
|
||||
|
||||
void UpdateInstance(OLIVE_NAMESPACE::RenderWorker* instance);
|
||||
|
||||
protected:
|
||||
virtual RenderWorker* CreateNewWorker() = 0;
|
||||
|
||||
@@ -77,15 +79,22 @@ protected:
|
||||
AudioRenderingParams audio_params() const;
|
||||
|
||||
private:
|
||||
RenderWorker *GetInstanceFromPool();
|
||||
RenderWorker *GetInstanceFromPool(QVector<RenderWorker*>& worker_pool,
|
||||
QThreadPool& thread_pool,
|
||||
int& instance_queuer);
|
||||
|
||||
ViewerOutput* viewer_node_;
|
||||
|
||||
RenderCancelDialog* cancel_dialog_;
|
||||
|
||||
QLinkedList<RenderWorker*> instance_pool_;
|
||||
QVector<RenderWorker*> video_instance_pool_;
|
||||
QVector<RenderWorker*> audio_instance_pool_;
|
||||
|
||||
QThreadPool thread_pool_;
|
||||
QThreadPool video_thread_pool_;
|
||||
QThreadPool audio_thread_pool_;
|
||||
|
||||
int video_instance_queuer_;
|
||||
int audio_instance_queuer_;
|
||||
|
||||
// VIDEO MEMBERS
|
||||
int divider_;
|
||||
@@ -107,7 +116,9 @@ private:
|
||||
QList<ConformWaitInfo> footage_wait_info_;
|
||||
|
||||
private slots:
|
||||
void AudioCallback();
|
||||
void AudioInvalidated(const TimeRange &r);
|
||||
|
||||
void AudioRendered();
|
||||
|
||||
void WorkerFinished();
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/***
|
||||
|
||||
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 RENDERCACHE_H
|
||||
#define RENDERCACHE_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
template<class K, class V>
|
||||
class RenderCache
|
||||
{
|
||||
public:
|
||||
RenderCache() = default;
|
||||
|
||||
void Clear(){values_.clear();}
|
||||
|
||||
void Add(K key, V val){values_.insert(key, val);}
|
||||
|
||||
V Get(K key) const {return values_.value(key);}
|
||||
|
||||
bool Has(K key) const {return values_.contains(key);}
|
||||
|
||||
void Remove(K key) {values_.remove(key);}
|
||||
|
||||
private:
|
||||
QHash<K, V> values_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // RENDERCACHE_H
|
||||
@@ -25,10 +25,12 @@
|
||||
#include "audio/sumsamples.h"
|
||||
#include "config/config.h"
|
||||
#include "node/block/clip/clip.h"
|
||||
#include "renderbackend.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
RenderWorker::RenderWorker() :
|
||||
RenderWorker::RenderWorker(RenderBackend* parent) :
|
||||
parent_(parent),
|
||||
viewer_(nullptr),
|
||||
available_(true)
|
||||
{
|
||||
@@ -39,12 +41,16 @@ RenderWorker::~RenderWorker()
|
||||
Close();
|
||||
}
|
||||
|
||||
QByteArray RenderWorker::Hash(const rational &time)
|
||||
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
|
||||
@@ -60,14 +66,18 @@ QByteArray RenderWorker::Hash(const rational &time)
|
||||
return hasher.result();
|
||||
}
|
||||
|
||||
FramePtr RenderWorker::RenderFrame(const rational &time)
|
||||
FramePtr RenderWorker::RenderFrame(const rational &time, bool block_for_update)
|
||||
{
|
||||
if (!viewer_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeValueTable table = GenerateTable(viewer_,
|
||||
TimeRange(time, time + video_params_.time_base()));
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
UpdateData(block_for_update);
|
||||
|
||||
NodeValueTable table = ProcessInput(viewer_->texture_input(),
|
||||
TimeRange(time, time + video_params_.time_base()));
|
||||
|
||||
QVariant texture = table.Get(NodeParam::kTexture);
|
||||
|
||||
@@ -89,10 +99,45 @@ FramePtr RenderWorker::RenderFrame(const rational &time)
|
||||
return frame;
|
||||
}
|
||||
|
||||
SampleBufferPtr RenderWorker::RenderAudio(const TimeRange &range)
|
||||
{
|
||||
if (!viewer_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QMutexLocker locker(&lock_);
|
||||
|
||||
UpdateData(true);
|
||||
|
||||
NodeValueTable table = ProcessInput(viewer_->samples_input(), range);
|
||||
|
||||
QVariant samples = table.Get(NodeParam::kSamples);
|
||||
|
||||
return samples.value<SampleBufferPtr>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const TimeRange &range)
|
||||
{
|
||||
qDebug() << "Hello from track" << track << "type" << track->track_type();
|
||||
|
||||
if (track->track_type() == Timeline::kTrackTypeAudio) {
|
||||
|
||||
qDebug() << "Hello?";
|
||||
|
||||
QList<Block*> active_blocks = track->BlocksAtTimeRange(range);
|
||||
|
||||
// All these blocks will need to output to a buffer so we create one here
|
||||
@@ -273,8 +318,8 @@ void RenderWorker::FootageProcessingEvent(StreamPtr stream, const TimeRange &inp
|
||||
NodeValue value;
|
||||
bool found_cache = false;
|
||||
|
||||
if (still_image_cache_.Has(stream.get())) {
|
||||
CachedStill cs = still_image_cache_.Get(stream.get());
|
||||
if (still_image_cache_.contains(stream.get())) {
|
||||
const CachedStill& cs = still_image_cache_[stream.get()];
|
||||
|
||||
if (cs.colorspace == colorspace_match
|
||||
&& cs.alpha_is_associated == video_stream->premultiplied_alpha()
|
||||
@@ -283,7 +328,7 @@ void RenderWorker::FootageProcessingEvent(StreamPtr stream, const TimeRange &inp
|
||||
value = cs.texture;
|
||||
found_cache = true;
|
||||
} else {
|
||||
still_image_cache_.Remove(stream.get());
|
||||
still_image_cache_.remove(stream.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,11 +336,11 @@ void RenderWorker::FootageProcessingEvent(StreamPtr stream, const TimeRange &inp
|
||||
|
||||
value = GetDataFromStream(stream, input_time);
|
||||
|
||||
still_image_cache_.Add(stream.get(), {value,
|
||||
colorspace_match,
|
||||
video_stream->premultiplied_alpha(),
|
||||
video_params_.divider(),
|
||||
time_match});
|
||||
still_image_cache_.insert(stream.get(), {value,
|
||||
colorspace_match,
|
||||
video_stream->premultiplied_alpha(),
|
||||
video_params_.divider(),
|
||||
time_match});
|
||||
|
||||
}
|
||||
|
||||
@@ -323,7 +368,7 @@ DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream)
|
||||
{
|
||||
// Access a map of Node inputs and decoder instances and retrieve a frame!
|
||||
|
||||
DecoderPtr decoder = decoder_cache_.Get(stream.get());
|
||||
DecoderPtr decoder = decoder_cache_.value(stream.get());
|
||||
|
||||
if (!decoder && stream) {
|
||||
// Create a new Decoder here
|
||||
@@ -331,7 +376,7 @@ DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream)
|
||||
decoder->set_stream(stream);
|
||||
|
||||
if (decoder->Open()) {
|
||||
decoder_cache_.Add(stream.get(), decoder);
|
||||
decoder_cache_.insert(stream.get(), decoder);
|
||||
} else {
|
||||
decoder = nullptr;
|
||||
qWarning() << "Failed to open decoder for" << stream->footage()->filename()
|
||||
|
||||
@@ -26,15 +26,16 @@
|
||||
#include "decodercache.h"
|
||||
#include "node/traverser.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "render/backend/rendercache.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class RenderBackend;
|
||||
|
||||
class RenderWorker : public QObject, public NodeTraverser
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RenderWorker();
|
||||
RenderWorker(RenderBackend* parent);
|
||||
|
||||
virtual ~RenderWorker() override;
|
||||
|
||||
@@ -84,7 +85,7 @@ public:
|
||||
*
|
||||
* SHA-1 hash or empty QByteArray if no viewer node is set.
|
||||
*/
|
||||
QByteArray Hash(const rational &time);
|
||||
QByteArray Hash(const rational &time, bool block_for_update);
|
||||
|
||||
/**
|
||||
* @brief Render the frame at this time
|
||||
@@ -97,11 +98,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);
|
||||
FramePtr RenderFrame(const rational& time, bool block_for_update);
|
||||
|
||||
SampleBufferPtr RenderAudio(ViewerOutput* viewer,
|
||||
const TimeRange& range,
|
||||
const SampleFormat::Format& sample_fmt);
|
||||
SampleBufferPtr RenderAudio(const TimeRange& range);
|
||||
|
||||
protected:
|
||||
virtual void TextureToFrame(const QVariant& texture, FramePtr frame, const QMatrix4x4 &mat) const = 0;
|
||||
@@ -130,6 +129,8 @@ signals:
|
||||
void FinishedJob();
|
||||
|
||||
private:
|
||||
void UpdateData(bool block_for_update);
|
||||
|
||||
NodeValue GetDataFromStream(StreamPtr stream, const TimeRange& input_time);
|
||||
|
||||
DecoderPtr ResolveDecoderFromInput(StreamPtr stream);
|
||||
@@ -138,6 +139,8 @@ private:
|
||||
Node *CopyNodeConnections(Node *src_node);
|
||||
void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input);
|
||||
|
||||
RenderBackend* parent_;
|
||||
|
||||
VideoRenderingParams video_params_;
|
||||
|
||||
AudioRenderingParams audio_params_;
|
||||
@@ -150,7 +153,7 @@ private:
|
||||
rational time;
|
||||
};
|
||||
|
||||
RenderCache<Stream*, CachedStill> still_image_cache_;
|
||||
QHash<Stream*, CachedStill> still_image_cache_;
|
||||
|
||||
QMatrix4x4 video_download_matrix_;
|
||||
|
||||
@@ -161,6 +164,8 @@ private:
|
||||
QHash<Node*, Node*> copy_map_;
|
||||
bool available_;
|
||||
|
||||
QMutex lock_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
Vendored
+30
-3
@@ -51,6 +51,11 @@ struct HashDownloadFuturePair {
|
||||
QFuture<void> download_future;
|
||||
};
|
||||
|
||||
struct HashTimePair {
|
||||
rational time;
|
||||
QByteArray hash;
|
||||
};
|
||||
|
||||
void CacheTask::Action()
|
||||
{
|
||||
OpenGLBackend backend;
|
||||
@@ -94,7 +99,7 @@ void CacheTask::Action()
|
||||
next = snapped + timebase;
|
||||
}
|
||||
|
||||
hash_list.append({snapped, backend.Hash(snapped)});
|
||||
hash_list.append({snapped, backend.Hash(snapped, false)});
|
||||
range_to_cache.RemoveTimeRange(TimeRange(snapped, next));
|
||||
}
|
||||
|
||||
@@ -107,11 +112,32 @@ void CacheTask::Action()
|
||||
// Render all frames necessary
|
||||
QLinkedList<HashFrameFuturePair> render_lookup_table;
|
||||
{
|
||||
QLinkedList<HashTimePair> sorted_times;
|
||||
QLinkedList<HashTimePair>::iterator sorted_iterator;
|
||||
|
||||
// Rendering is more efficient if we cache in order
|
||||
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();
|
||||
|
||||
render_lookup_table.append({hash, backend.RenderFrame(i.value().first(), false)});
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const HashTimePair& p, sorted_times) {
|
||||
render_lookup_table.append({p.hash, backend.RenderFrame(p.time, false, false)});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +150,7 @@ void CacheTask::Action()
|
||||
// Start downloading frames that have finished
|
||||
{
|
||||
int counter = 0;
|
||||
int nb_frames = render_lookup_table.size();
|
||||
|
||||
QLinkedList<HashDownloadFuturePair> download_futures;
|
||||
|
||||
@@ -160,7 +187,7 @@ void CacheTask::Action()
|
||||
|
||||
// Signal process
|
||||
counter++;
|
||||
emit ProgressChanged(qRound(100.0 * static_cast<double>(counter) / static_cast<double>(render_lookup_table.size())));
|
||||
emit ProgressChanged(qRound(100.0 * static_cast<double>(counter) / static_cast<double>(nb_frames)));
|
||||
|
||||
j = download_futures.erase(j);
|
||||
} else {
|
||||
|
||||
@@ -418,7 +418,7 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time)
|
||||
this,
|
||||
&ViewerWidget::RendererGeneratedFrame);
|
||||
|
||||
watcher->setFuture(GetFrame(time, true));
|
||||
watcher->setFuture(GetFrame(time, true, true));
|
||||
}
|
||||
|
||||
void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
@@ -525,7 +525,7 @@ void ViewerWidget::FillPlaybackQueue()
|
||||
rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_,
|
||||
timebase());
|
||||
playback_queue_next_frame_ += playback_speed_;
|
||||
QFuture<FramePtr> future = GetFrame(next_time, false);
|
||||
QFuture<FramePtr> future = GetFrame(next_time, false, false);
|
||||
future.waitForFinished();
|
||||
playback_queue_.AppendTimewise({future.result()->timestamp(), future.result()}, playback_speed_);
|
||||
|
||||
@@ -552,7 +552,7 @@ QString ViewerWidget::GetCachedFilenameFromTime(const rational &time)
|
||||
|
||||
bool ViewerWidget::FrameExistsAtTime(const rational &time)
|
||||
{
|
||||
return GetConnectedNode() && time < GetConnectedNode()->GetLength();
|
||||
return GetConnectedNode() && time >= 0 && time < GetConnectedNode()->GetLength();
|
||||
}
|
||||
|
||||
void ViewerWidget::SetDisplayImage(FramePtr frame, bool main_only)
|
||||
@@ -584,7 +584,7 @@ void ViewerWidget::RequestNextFrameForQueue()
|
||||
&QFutureWatcher<FramePtr>::finished,
|
||||
this,
|
||||
&ViewerWidget::RendererGeneratedFrameForQueue);
|
||||
watcher->setFuture(GetFrame(next_time, false));
|
||||
watcher->setFuture(GetFrame(next_time, false, true));
|
||||
}
|
||||
|
||||
PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const
|
||||
@@ -592,12 +592,12 @@ PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const
|
||||
return PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline);
|
||||
}
|
||||
|
||||
QFuture<FramePtr> ViewerWidget::GetFrame(const rational &t, bool clear_render_queue)
|
||||
QFuture<FramePtr> ViewerWidget::GetFrame(const rational &t, bool clear_render_queue, bool block_update)
|
||||
{
|
||||
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);
|
||||
return renderer_->RenderFrame(t, clear_render_queue, block_update);
|
||||
} else {
|
||||
// Frame has been cached, grab the frame
|
||||
QString cache_fn = GetConnectedNode()->video_frame_cache()->CachePathName(cached_hash,
|
||||
|
||||
@@ -188,7 +188,7 @@ private:
|
||||
|
||||
PixelFormat::Format GetCurrentPixelFormat() const;
|
||||
|
||||
QFuture<FramePtr> GetFrame(const rational& t, bool clear_render_queue);
|
||||
QFuture<FramePtr> GetFrame(const rational& t, bool clear_render_queue, bool block_update);
|
||||
|
||||
QStackedWidget* stack_;
|
||||
|
||||
|
||||
@@ -38,17 +38,18 @@ public:
|
||||
|
||||
void AppendTimewise(const ViewerPlaybackFrame& f, int playback_speed)
|
||||
{
|
||||
if (this->isEmpty() || f.timestamp > this->last().timestamp == playback_speed > 0) {
|
||||
reverse_iterator i = this->rbegin();
|
||||
|
||||
if (this->isEmpty() || (this->last().timestamp < f.timestamp) == (playback_speed > 0)) {
|
||||
this->append(f);
|
||||
} else {
|
||||
reverse_iterator i = this->rbegin();
|
||||
|
||||
while (i != this->rend()) {
|
||||
reverse_iterator next = i + 1;
|
||||
|
||||
if (next->timestamp < f.timestamp == playback_speed > 0) {
|
||||
if ((next->timestamp < f.timestamp) == (playback_speed > 0)) {
|
||||
this->insert(i.base(), f);
|
||||
break;
|
||||
|
||||
return;
|
||||
} else {
|
||||
i = next;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user