From f1e4e1c6856d0d287e85fb0ae0b64386c94ed0b6 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 22 Oct 2019 13:22:30 +1100 Subject: [PATCH] added samples type and duplicated video renderer --- app/node/output/viewer/viewer.cpp | 9 + app/node/output/viewer/viewer.h | 4 + app/node/param.cpp | 2 + app/node/param.h | 3 + app/render/audio/CMakeLists.txt | 8 +- app/render/audio/audiorenderer.cpp | 420 +++++++++++---- app/render/audio/audiorenderer.h | 103 ++-- app/render/audio/audiorendererparams.cpp | 23 - app/render/audio/audiorendererparams.h | 28 - app/render/audio/audiorendererthread.cpp | 23 - app/render/audio/audiorendererthread.h | 26 - app/render/audio/audiorendererthreadbase.cpp | 26 +- app/render/audio/audiorendererthreadbase.h | 25 +- app/render/video/videorendermanager.cpp | 477 ------------------ app/render/video/videorendermanager.h | 218 -------- .../nodeparamviewwidgetbridge.cpp | 2 + 16 files changed, 440 insertions(+), 957 deletions(-) delete mode 100644 app/render/audio/audiorendererparams.cpp delete mode 100644 app/render/audio/audiorendererparams.h delete mode 100644 app/render/audio/audiorendererthread.cpp delete mode 100644 app/render/audio/audiorendererthread.h delete mode 100644 app/render/video/videorendermanager.cpp delete mode 100644 app/render/video/videorendermanager.h diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 124c861c7..f28165b16 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -28,6 +28,10 @@ ViewerOutput::ViewerOutput() : texture_input_->add_data_input(NodeInput::kTexture); AddParameter(texture_input_); + samples_input_ = new NodeInput("samples_in"); + samples_input_->add_data_input(NodeInput::kSamples); + AddParameter(samples_input_); + length_input_ = new NodeInput("length_in"); length_input_->add_data_input(NodeInput::kRational); AddParameter(length_input_); @@ -70,6 +74,11 @@ NodeInput *ViewerOutput::texture_input() return texture_input_; } +NodeInput *ViewerOutput::samples_input() +{ + return samples_input_; +} + NodeInput *ViewerOutput::length_input() { return length_input_; diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index d1fb864ff..d29f159dc 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -44,9 +44,11 @@ public: void SetTimebase(const rational& timebase); NodeInput* texture_input(); + NodeInput* samples_input(); NodeInput* length_input(); RenderTexturePtr GetTexture(const rational& time); + QByteArray GetSamples(const rational& in, const rational& out); virtual void InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from = nullptr) override; @@ -70,6 +72,8 @@ protected: private: NodeInput* texture_input_; + NodeInput* samples_input_; + NodeInput* length_input_; rational timebase_; diff --git a/app/node/param.cpp b/app/node/param.cpp index 8a793db14..cb08f0ac4 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -251,6 +251,7 @@ QString NodeParam::GetDefaultDataTypeName(const DataType& type) case kVec2: return tr("Vector2D"); case kVec3: return tr("Vector3D"); case kVec4: return tr("Vector4D"); + case kSamples: return tr("Samples"); case kAny: return tr("Any"); } @@ -279,6 +280,7 @@ QByteArray NodeParam::ValueToBytes(const NodeParam::DataType &type, const QVaria case kTexture: case kBlock: case kTrack: + case kSamples: case kAny: break; } diff --git a/app/node/param.h b/app/node/param.h index d11d2ba31..86fc49853 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -105,6 +105,9 @@ public: /// Resolves to `QVector4D` kVec4, + /// Resolves to `QByteArray` + kSamples, + kAny }; diff --git a/app/render/audio/CMakeLists.txt b/app/render/audio/CMakeLists.txt index 826055ece..f29c143c9 100644 --- a/app/render/audio/CMakeLists.txt +++ b/app/render/audio/CMakeLists.txt @@ -18,10 +18,10 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} render/audio/audiorenderer.h render/audio/audiorenderer.cpp - render/audio/audiorendererparams.h - render/audio/audiorendererparams.cpp - render/audio/audiorendererthread.h - render/audio/audiorendererthread.cpp + render/audio/audiorendererdownloadthread.h + render/audio/audiorendererdownloadthread.cpp + render/audio/audiorendererprocessthread.h + render/audio/audiorendererprocessthread.cpp render/audio/audiorendererthreadbase.h render/audio/audiorendererthreadbase.cpp PARENT_SCOPE diff --git a/app/render/audio/audiorenderer.cpp b/app/render/audio/audiorenderer.cpp index d30138840..b92722390 100644 --- a/app/render/audio/audiorenderer.cpp +++ b/app/render/audio/audiorenderer.cpp @@ -20,6 +20,7 @@ #include "audiorenderer.h" +#include #include #include #include @@ -28,44 +29,28 @@ #include #include "common/filefunctions.h" +#include "render/gl/functions.h" +#include "render/gl/shadergenerators.h" #include "render/pixelservice.h" -AudioRendererProcessor::AudioRendererProcessor() : +AudioRendererProcessor::AudioRendererProcessor(QObject *parent) : + QObject(parent), started_(false), + width_(0), + height_(0), + divider_(1), caching_(false), - starting_(false) + push_time_(-1), + starting_(false), + viewer_node_(nullptr) { - texture_input_ = new NodeInput("tex_in"); - texture_input_->add_data_input(NodeInput::kTexture); - AddParameter(texture_input_); - - length_input_ = new NodeInput("length_in"); - length_input_->add_data_input(NodeInput::kRational); - AddParameter(length_input_); - - texture_output_ = new NodeOutput("tex_out"); - texture_output_->set_data_type(NodeInput::kTexture); - AddParameter(texture_output_); + // FIXME: Cache name should actually be the name of the sequence + SetCacheName("Test"); } -QString AudioRendererProcessor::Name() +AudioRendererProcessor::~AudioRendererProcessor() { - return tr("Audio Renderer"); -} - -QString AudioRendererProcessor::Category() -{ - return tr("Processor"); -} - -QString AudioRendererProcessor::Description() -{ - return tr("A multi-threaded PCM audio renderer."); -} - -QString AudioRendererProcessor::id() -{ - return "org.olivevideoeditor.Olive.rendererneptune"; + Stop(); } void AudioRendererProcessor::SetCacheName(const QString &s) @@ -76,72 +61,15 @@ void AudioRendererProcessor::SetCacheName(const QString &s) GenerateCacheIDInternal(); } -QVariant AudioRendererProcessor::Value(NodeOutput* output, const rational& time) +void AudioRendererProcessor::InvalidateCache(const rational &start_range, const rational &end_range) { - Q_UNUSED(output) - Q_UNUSED(time) - - /*if (output == texture_output_) { - if (!texture_input_->IsConnected()) { - // Nothing is connected - nothing to show or render - return 0; - } - - if (cache_id_.isEmpty()) { - qWarning() << "RendererProcessor has no cache ID"; - return 0; - } - - if (timebase_.isNull()) { - qWarning() << "RendererProcessor has no timebase"; - return 0; - } - - // Find frame in map - if (time_hash_map_.contains(time)) { - QString fn = CachePathName(time_hash_map_[time]); - - if (QFileInfo::exists(fn)) { - auto in = OIIO::ImageInput::open(fn.toStdString()); - - if (in) { - in->read_image(PixelService::GetPixelFormatInfo(format_).oiio_desc, cache_frame_load_buffer_.data()); - - in->close(); - - master_texture_->Upload(cache_frame_load_buffer_.data()); - - return QVariant::fromValue(master_texture_); - } else { - qWarning() << "OIIO Error:" << OIIO::geterror().c_str(); - } - } - } - }*/ - - return 0; -} - -void AudioRendererProcessor::Release() -{ - Stop(); -} - -void AudioRendererProcessor::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from) -{ - Q_UNUSED(from) - if (timebase_.isNull()) { return; } - //ClearCachedValuesInParameters(start_range, end_range); - texture_input_->ClearCachedValue(); - length_input_->ClearCachedValue(); - // Adjust range to min/max values rational start_range_adj = qMax(rational(0), start_range); - rational end_range_adj = qMin(length_input()->get_value(0).value(), end_range); + rational end_range_adj = qMin(viewer_node_->Length(), end_range); qDebug() << "Cache invalidated between" << start_range_adj.toDouble() @@ -156,7 +84,7 @@ void AudioRendererProcessor::InvalidateCache(const rational &start_range, const for (rational r=true_start_range;r<=end_range_adj;r+=timebase_) { // Try to order the queue from closest to the playhead to furthest - rational last_time = texture_output()->LastRequestedTime(); + rational last_time = last_time_requested_; rational diff = r - last_time; @@ -207,18 +135,42 @@ void AudioRendererProcessor::SetTimebase(const rational &timebase) timebase_dbl_ = timebase_.toDouble(); } -void AudioRendererProcessor::SetParameters(const int &sample_rate, - const uint64_t &channel_layout, - const olive::SampleFormat &format) +void AudioRendererProcessor::SetParameters(const int &width, + const int &height, + const olive::PixelFormat &format, + const olive::RenderMode &mode, + const int& divider) { // Since we're changing parameters, all the existing threads are invalid and must be removed. They will start again // next time this Node has to process anything. Stop(); // Set new parameters - sample_rate_ = sample_rate; - channel_layout_ = channel_layout; + width_ = width; + height_ = height; format_ = format; + mode_ = mode; + + // divider's default value is 0, so we can assume if it's 0 a divider wasn't specified + if (divider > 0) { + divider_ = divider; + } + + CalculateEffectiveDimensions(); + + // Regenerate the cache ID + GenerateCacheIDInternal(); +} + +void AudioRendererProcessor::SetDivider(const int ÷r) +{ + Q_ASSERT(divider_ > 0); + + Stop(); + + divider_ = divider; + + CalculateEffectiveDimensions(); // Regenerate the cache ID GenerateCacheIDInternal(); @@ -230,12 +182,18 @@ void AudioRendererProcessor::Start() return; } + QOpenGLContext* ctx = QOpenGLContext::currentContext(); + int background_thread_count = QThread::idealThreadCount(); + // Some OpenGL implementations (notably wgl) require the context not to be current before sharing + QSurface* old_surface = ctx->surface(); + ctx->doneCurrent(); + threads_.resize(background_thread_count); for (int i=0;i(sample_rate_, channel_layout_, format_); + threads_[i] = std::make_shared(this, ctx, effective_width_, effective_height_, divider_, format_, mode_); threads_[i]->StartThread(QThread::LowPriority); // Ensure this connection is "Queued" so that it always runs in this object's threaded rather than any of the @@ -259,6 +217,36 @@ void AudioRendererProcessor::Start() SLOT(ThreadSkippedFrame(const rational&, const QByteArray&)), Qt::QueuedConnection); + download_threads_.resize(background_thread_count); + + for (int i=0;i(ctx, effective_width_, effective_height_, divider_, format_, mode_); + download_threads_[i]->StartThread(QThread::LowPriority); + + connect(download_threads_[i].get(), + SIGNAL(Downloaded(const QByteArray&)), + this, + SLOT(DownloadThreadComplete(const QByteArray&)), + Qt::QueuedConnection); + } + + last_download_thread_ = 0; + + // Restore context now that thread creation is complete + ctx->makeCurrent(old_surface); + + // Create master texture (the one sent to the viewer) + master_texture_ = std::make_shared(); + master_texture_->Create(ctx, effective_width_, effective_height_, format_); + + // Create internal FBO for copying textures + copy_buffer_.Create(ctx); + copy_buffer_.Attach(master_texture_); + copy_pipeline_ = olive::ShaderGenerator::DefaultPipeline(); + + cache_frame_load_buffer_.resize(PixelService::GetBufferSize(format_, effective_width_, effective_height_)); + started_ = true; } @@ -270,15 +258,26 @@ void AudioRendererProcessor::Stop() started_ = false; - foreach (AudioRendererThreadPtr process_thread, threads_) { + foreach (AudioRendererDownloadThreadPtr download_thread_, download_threads_) { + download_thread_->Cancel(); + } + download_threads_.clear(); + + foreach (AudioRendererProcessThreadPtr process_thread, threads_) { process_thread->Cancel(); } threads_.clear(); + + copy_buffer_.Destroy(); + master_texture_ = nullptr; + copy_pipeline_ = nullptr; + + cache_frame_load_buffer_.clear(); } void AudioRendererProcessor::GenerateCacheIDInternal() { - if (cache_name_.isEmpty() || sample_rate_ == 0 || channel_layout_ == 0) { + if (cache_name_.isEmpty() || effective_width_ == 0 || effective_height_ == 0) { return; } @@ -286,9 +285,10 @@ void AudioRendererProcessor::GenerateCacheIDInternal() QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(cache_name_.toUtf8()); hash.addData(QString::number(cache_time_).toUtf8()); - hash.addData(QString::number(sample_rate_).toUtf8()); - hash.addData(QString::number(channel_layout_).toUtf8()); + hash.addData(QString::number(width_).toUtf8()); + hash.addData(QString::number(height_).toUtf8()); hash.addData(QString::number(format_).toUtf8()); + hash.addData(QString::number(divider_).toUtf8()); QByteArray bytes = hash.result(); cache_id_ = bytes.toHex(); @@ -296,7 +296,7 @@ void AudioRendererProcessor::GenerateCacheIDInternal() void AudioRendererProcessor::CacheNext() { - if (cache_queue_.isEmpty() || !texture_input_->IsConnected() || caching_) { + if (cache_queue_.isEmpty() || viewer_node_ == nullptr || caching_) { return; } @@ -307,36 +307,232 @@ void AudioRendererProcessor::CacheNext() qDebug() << "Caching" << cache_frame.toDouble(); + threads_.first()->Queue(NodeDependency(viewer_node_->texture_input()->get_connected_output(), cache_frame), true, false); + caching_ = true; } -AudioRendererThreadBase *AudioRendererProcessor::CurrentThread() +QString AudioRendererProcessor::CachePathName(const QByteArray &hash) +{ + QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id_); + this_cache_dir.mkpath("."); + + QString filename = QString("%1.exr").arg(QString(hash.toHex())); + + return this_cache_dir.filePath(filename); +} + +void AudioRendererProcessor::DeferMap(const rational &time, const QByteArray &hash) +{ + deferred_maps_.append({time, hash}); +} + +bool AudioRendererProcessor::HasHash(const QByteArray &hash) +{ + return QFileInfo::exists(CachePathName(hash)); +} + +bool AudioRendererProcessor::IsCaching(const QByteArray &hash) +{ + cache_hash_list_mutex_.lock(); + + bool is_caching = cache_hash_list_.contains(hash); + + cache_hash_list_mutex_.unlock(); + + return is_caching; +} + +bool AudioRendererProcessor::TryCache(const QByteArray &hash) +{ + cache_hash_list_mutex_.lock(); + + bool is_caching = cache_hash_list_.contains(hash); + + if (!is_caching) { + cache_hash_list_.append(hash); + } + + cache_hash_list_mutex_.unlock(); + + return !is_caching; +} + +void AudioRendererProcessor::CalculateEffectiveDimensions() +{ + effective_width_ = width_ / divider_; + effective_height_ = height_ / divider_; +} + +void AudioRendererProcessor::ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash) +{ + // Threads are all done now, time to proceed + caching_ = false; + + DeferMap(time, hash); + + if (texture != nullptr) { + // We received a texture, time to start downloading it + QString fn = CachePathName(hash); + + download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture, + fn, + hash); + + last_download_thread_++; + } else { + // There was no texture here, we must update the viewer + DownloadThreadComplete(hash); + } + + // If the connected output is using this time, signal it to update + if (last_time_requested_ == time) { + copy_buffer_.Bind(); + texture->Bind(); + + QOpenGLContext::currentContext()->functions()->glViewport(0, 0, master_texture_->width(), master_texture_->height()); + + olive::gl::Blit(copy_pipeline_); + + texture->Release(); + copy_buffer_.Release(); + + push_time_ = time; + + emit CachedFrameReady(time); + } + + CacheNext(); +} + +void AudioRendererProcessor::ThreadRequestSibling(NodeDependency dep) +{ + // Try to queue another thread to run this dep in advance + for (int i=1;iQueue(dep, false, true)) { + return; + } + } +} + +void AudioRendererProcessor::ThreadSkippedFrame(const rational& time, const QByteArray& hash) +{ + caching_ = false; + + DeferMap(time, hash); + + if (!IsCaching(hash)) { + DownloadThreadComplete(hash); + + // Signal output to update value + emit CachedFrameReady(time); + } + + CacheNext(); +} + +void AudioRendererProcessor::DownloadThreadComplete(const QByteArray &hash) +{ + cache_hash_list_mutex_.lock(); + cache_hash_list_.removeAll(hash); + cache_hash_list_mutex_.unlock(); + + for (int i=0;i(QThread::currentThread()); } -AudioRendererParams *AudioRendererProcessor::CurrentInstance() +RenderInstance *AudioRendererProcessor::CurrentInstance() { AudioRendererThreadBase* thread = CurrentThread(); if (thread != nullptr) { - return thread->params(); + return thread->render_instance(); } return nullptr; } -NodeInput *AudioRendererProcessor::texture_input() +RenderTexturePtr AudioRendererProcessor::GetCachedFrame(const rational &time) { - return texture_input_; + last_time_requested_ = time; + + if (push_time_ >= 0) { + rational temp_push_time = push_time_; + push_time_ = -1; + + if (time == temp_push_time) { + return master_texture_; + } + } + + if (viewer_node_ == nullptr) { + // Nothing is connected - nothing to show or render + return nullptr; + } + + if (cache_id_.isEmpty()) { + qWarning() << "RendererProcessor has no cache ID"; + return nullptr; + } + + if (timebase_.isNull()) { + qWarning() << "RendererProcessor has no timebase"; + return nullptr; + } + + // Find frame in map + if (time_hash_map_.contains(time)) { + QString fn = CachePathName(time_hash_map_[time]); + + if (QFileInfo::exists(fn)) { + auto in = OIIO::ImageInput::open(fn.toStdString()); + + if (in) { + in->read_image(PixelService::GetPixelFormatInfo(format_).oiio_desc, cache_frame_load_buffer_.data()); + + in->close(); + + master_texture_->Upload(cache_frame_load_buffer_.data()); + + return master_texture_; + } else { + qWarning() << "OIIO Error:" << OIIO::geterror().c_str(); + } + } + } + + return nullptr; } -NodeInput *AudioRendererProcessor::length_input() +void AudioRendererProcessor::SetViewerNode(ViewerOutput *viewer) { - return length_input_; -} + if (viewer_node_ != nullptr) { + disconnect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&))); + } -NodeOutput *AudioRendererProcessor::texture_output() -{ - return texture_output_; + viewer_node_ = viewer; + + if (viewer_node_ != nullptr) { + connect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&))); + + // FIXME: Hardcoded format and mode + SetParameters(viewer_node_->ViewerWidth(), + viewer_node_->ViewerHeight(), + olive::PIX_FMT_RGBA16F, + olive::kOffline); + } } diff --git a/app/render/audio/audiorenderer.h b/app/render/audio/audiorenderer.h index 8882ccf5a..b3f047d46 100644 --- a/app/render/audio/audiorenderer.h +++ b/app/render/audio/audiorenderer.h @@ -24,15 +24,16 @@ #include #include -#include "node/node.h" +#include "node/output/viewer/viewer.h" #include "render/pixelformat.h" #include "render/rendermodes.h" -#include "audiorendererthread.h" +#include "audiorendererdownloadthread.h" +#include "audiorendererprocessthread.h" /** - * @brief A multithreaded PCM audio renderer + * @brief A multithreaded OpenGL based renderer for node systems */ -class AudioRendererProcessor : public Node +class AudioRendererProcessor : public QObject { Q_OBJECT public: @@ -42,19 +43,12 @@ public: * Constructing a Renderer object will not start any threads/backend on its own. Use Start() to do this and Stop() * when the Renderer is about to be destroyed. */ - AudioRendererProcessor(); + AudioRendererProcessor(QObject* parent); - virtual QString Name() override; - virtual QString Category() override; - virtual QString Description() override; - virtual QString id() override; + virtual ~AudioRendererProcessor() override; void SetCacheName(const QString& s); - virtual void Release() override; - - virtual void InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from = nullptr) override; - void SetTimebase(const rational& timebase); /** @@ -75,9 +69,18 @@ public: * * Buffer pixel format */ - void SetParameters(const int& sample_rate, - const uint64_t& channel_layout, - const olive::SampleFormat& format); + void SetParameters(const int& width, + const int& height, + const olive::PixelFormat& format, + const olive::RenderMode& mode, + const int ÷r = 0); + + void SetDivider(const int& divider); + + /** + * @brief Return whether a frame with this hash already exists + */ + bool HasHash(const QByteArray& hash); /** * @brief Return whether a frame is currently being cached @@ -97,16 +100,14 @@ public: */ static AudioRendererThreadBase* CurrentThread(); - static AudioRendererParams* CurrentInstance(); + static RenderInstance* CurrentInstance(); - NodeInput* texture_input(); + RenderTexturePtr GetCachedFrame(const rational& time); - NodeInput* length_input(); + void SetViewerNode(ViewerOutput* viewer); - NodeOutput* texture_output(); - -protected: - virtual QVariant Value(NodeOutput* output, const rational& time) override; +signals: + void CachedFrameReady(const rational& time); private: struct HashTimeMapping { @@ -138,39 +139,79 @@ private: bool ShouldPushTexture(const rational &time); + /** + * @brief Return the path of the cached image at this time + */ + QString CachePathName(const QByteArray &hash); + + void DeferMap(const rational &time, const QByteArray &hash); + /** * @brief Internal list of RenderProcessThreads */ - QVector threads_; + QVector threads_; /** * @brief Internal variable that contains whether the Renderer has started or not */ bool started_; - NodeInput* texture_input_; + int width_; + int height_; - NodeInput* length_input_; + void CalculateEffectiveDimensions(); - NodeOutput* texture_output_; + int divider_; + int effective_width_; + int effective_height_; + + olive::PixelFormat format_; + + olive::RenderMode mode_; + + rational last_time_requested_; rational timebase_; double timebase_dbl_; - int sample_rate_; - uint64_t channel_layout_; - olive::SampleFormat format_; - QLinkedList cache_queue_; QString cache_name_; qint64 cache_time_; QString cache_id_; bool caching_; + QVector cache_frame_load_buffer_; + + QVector download_threads_; + int last_download_thread_; + + RenderTexturePtr master_texture_; + rational push_time_; + + RenderFramebuffer copy_buffer_; + ShaderPtr copy_pipeline_; + + QMap time_hash_map_; + + QMutex cache_hash_list_mutex_; + QVector cache_hash_list_; + + QList deferred_maps_; bool starting_; + ViewerOutput* viewer_node_; + private slots: + void InvalidateCache(const rational &start_range, const rational &end_range); + + void ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash); + + void ThreadRequestSibling(NodeDependency dep); + + void ThreadSkippedFrame(const rational &time, const QByteArray &hash); + + void DownloadThreadComplete(const QByteArray &hash); }; diff --git a/app/render/audio/audiorendererparams.cpp b/app/render/audio/audiorendererparams.cpp deleted file mode 100644 index 612cba7be..000000000 --- a/app/render/audio/audiorendererparams.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "audiorendererparams.h" - -AudioRendererParams::AudioRendererParams(const int &sample_rate, const uint64_t &channel_layout, const olive::SampleFormat &format) : - sample_rate_(sample_rate), - channel_layout_(channel_layout), - format_(format) -{ -} - -const int &AudioRendererParams::sample_rate() -{ - return sample_rate_; -} - -const uint64_t &AudioRendererParams::channel_layout() -{ - return channel_layout_; -} - -const olive::SampleFormat &AudioRendererParams::format() -{ - return format_; -} diff --git a/app/render/audio/audiorendererparams.h b/app/render/audio/audiorendererparams.h deleted file mode 100644 index 7a3f7ac67..000000000 --- a/app/render/audio/audiorendererparams.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef AUDIORENDERERPARAMS_H -#define AUDIORENDERERPARAMS_H - -#include - -#include "audio/sampleformat.h" - -class AudioRendererParams -{ -public: - AudioRendererParams(const int& sample_rate, const uint64_t& channel_layout, const olive::SampleFormat& format); - - const int& sample_rate(); - - const uint64_t& channel_layout(); - - const olive::SampleFormat& format(); - -private: - int sample_rate_; - - uint64_t channel_layout_; - - olive::SampleFormat format_; - -}; - -#endif // AUDIORENDERERPARAMS_H diff --git a/app/render/audio/audiorendererthread.cpp b/app/render/audio/audiorendererthread.cpp deleted file mode 100644 index 04a63c167..000000000 --- a/app/render/audio/audiorendererthread.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "audiorendererthread.h" - -AudioRendererThread::AudioRendererThread(const int &sample_rate, const uint64_t &channel_layout, const olive::SampleFormat &format) : - AudioRendererThreadBase(sample_rate, channel_layout, format), - cancelled_(false) -{ -} - -void AudioRendererThread::Cancel() -{ - cancelled_ = true; - - mutex_.lock(); - wait_cond_.wakeAll(); - mutex_.unlock(); - - wait(); -} - -void AudioRendererThread::ProcessLoop() -{ - -} diff --git a/app/render/audio/audiorendererthread.h b/app/render/audio/audiorendererthread.h deleted file mode 100644 index 269fdc32c..000000000 --- a/app/render/audio/audiorendererthread.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef AUDIORENDERERTHREAD_H -#define AUDIORENDERERTHREAD_H - -#include "audiorendererthreadbase.h" - -class AudioRendererThread : public AudioRendererThreadBase -{ - Q_OBJECT -public: - AudioRendererThread(const int& sample_rate, - const uint64_t& channel_layout, - const olive::SampleFormat& format); - -public slots: - virtual void Cancel() override; - -protected: - virtual void ProcessLoop() override; - -private: - bool cancelled_; -}; - -using AudioRendererThreadPtr = std::shared_ptr; - -#endif // AUDIORENDERERTHREAD_H diff --git a/app/render/audio/audiorendererthreadbase.cpp b/app/render/audio/audiorendererthreadbase.cpp index 0dd565449..e1544c424 100644 --- a/app/render/audio/audiorendererthreadbase.cpp +++ b/app/render/audio/audiorendererthreadbase.cpp @@ -22,14 +22,16 @@ #include -AudioRendererThreadBase::AudioRendererThreadBase(const int &sample_rate, const uint64_t &channel_layout, const olive::SampleFormat &format) : - audio_params_(sample_rate, channel_layout, format) +AudioRendererThreadBase::AudioRendererThreadBase(QOpenGLContext *share_ctx, const int &width, const int &height, const int ÷r, const olive::PixelFormat &format, const olive::RenderMode &mode) : + share_ctx_(share_ctx), + render_instance_(width, height, divider, format, mode) { + connect(share_ctx_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Cancel())); } -AudioRendererParams *AudioRendererThreadBase::params() +RenderInstance *AudioRendererThreadBase::render_instance() { - return &audio_params_; + return &render_instance_; } void AudioRendererThreadBase::run() @@ -37,11 +39,23 @@ void AudioRendererThreadBase::run() // Lock mutex for main loop mutex_.lock(); + render_instance_.SetShareContext(share_ctx_); + + // Allocate and create resources + bool started = render_instance_.Start(); + // Signal that main thread can continue now WakeCaller(); - // Main loop (use Cancel() to exit it) - ProcessLoop(); + if (started) { + + // Main loop (use Cancel() to exit it) + ProcessLoop(); + + } + + // Free all resources + render_instance_.Stop(); // Unlock mutex before exiting mutex_.unlock(); diff --git a/app/render/audio/audiorendererthreadbase.h b/app/render/audio/audiorendererthreadbase.h index fd242a2ca..5e6e72872 100644 --- a/app/render/audio/audiorendererthreadbase.h +++ b/app/render/audio/audiorendererthreadbase.h @@ -18,26 +18,29 @@ ***/ -#ifndef AUDIORENDERTHREADBASE_H -#define AUDIORENDERTHREADBASE_H +#ifndef AUDIORENDERTHREAD_H +#define AUDIORENDERTHREAD_H #include #include #include #include -#include "audiorendererparams.h" #include "node/node.h" +#include "render/renderinstance.h" class AudioRendererThreadBase : public QThread { Q_OBJECT public: - AudioRendererThreadBase(const int& sample_rate, - const uint64_t& channel_layout, - const olive::SampleFormat& format); + AudioRendererThreadBase(QOpenGLContext* share_ctx, + const int& width, + const int& height, + const int& divider, + const olive::PixelFormat& format, + const olive::RenderMode& mode); - AudioRendererParams* params(); + RenderInstance* render_instance(); void StartThread(Priority priority = InheritPriority); @@ -58,8 +61,12 @@ protected: private: void WakeCaller(); - AudioRendererParams audio_params_; + QOpenGLContext* share_ctx_; + + RenderInstance render_instance_; }; -#endif // RENDERTHREAD_H +using AudioRendererThreadPtr = std::shared_ptr; + +#endif // AUDIORENDERTHREAD_H diff --git a/app/render/video/videorendermanager.cpp b/app/render/video/videorendermanager.cpp deleted file mode 100644 index 09342e301..000000000 --- a/app/render/video/videorendermanager.cpp +++ /dev/null @@ -1,477 +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 . - -***/ - -#include "rendermanager.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "common/filefunctions.h" -#include "render/pixelservice.h" - -RenderManager* RenderManager::instance_ = nullptr; - -RenderManager::RenderManager() : - started_(false), - width_(0), - height_(0), - divider_(1), - caching_(false), - texture_input_(nullptr) -{ -} - -void RenderManager::CreateInstance() -{ - if (instance_ == nullptr) { - instance_ = new RenderManager(); - } -} - -void RenderManager::DestroyInstance() -{ - delete instance_; - instance_ = nullptr; -} - -RenderManager *RenderManager::instance() -{ - return instance_; -} - -void RenderManager::SetCacheName(const QString &s) -{ - cache_name_ = s; - cache_time_ = QDateTime::currentMSecsSinceEpoch(); - - GenerateCacheIDInternal(); -} - -void RenderManager::InvalidateCache(const rational &start_range, const rational &end_range, const rational& last_requested_time) -{ - if (timebase_.isNull()) { - return; - } - - - - qDebug() << "Cache invalidated between" - << start_range.toDouble() - << "and" - << end_range.toDouble(); - - // Snap start_range to timebase - double start_range_dbl = start_range.toDouble(); - double start_range_numf = start_range_dbl * static_cast(timebase_.denominator()); - int64_t start_range_numround = qFloor(start_range_numf/static_cast(timebase_.numerator())) * timebase_.numerator(); - rational true_start_range(start_range_numround, timebase_.denominator()); - - for (rational r=true_start_range;r<=end_range;r+=timebase_) { - // Try to order the queue from closest to the playhead to furthest - rational diff = r - last_requested_time; - - if (diff < 0) { - // FIXME: Hardcoded number - // If the number is before the playhead, we still prioritize its closeness but not nearly as much (5:1 in this - // example) - diff = qAbs(diff) * 5; - } - - bool contains = false; - bool added = false; - QLinkedList::iterator insert_iterator; - - for (QLinkedList::iterator i = cache_queue_.begin();i != cache_queue_.end();i++) { - rational compare = *i; - - if (!added) { - rational compare_diff = compare - last_requested_time; - - if (compare_diff > diff) { - insert_iterator = i; - added = true; - } - } - - if (compare == r) { - contains = true; - break; - } - } - - if (!contains) { - if (added) { - cache_queue_.insert(insert_iterator, r); - } else { - cache_queue_.append(r); - } - } - } - - CacheNext(); -} - -void RenderManager::SetTimebase(const rational &timebase) -{ - timebase_ = timebase; - timebase_dbl_ = timebase_.toDouble(); -} - -void RenderManager::SetParameters(const int &width, - const int &height, - const olive::PixelFormat &format, - const olive::RenderMode &mode, - NodeInput* input, - const int& divider) -{ - // Since we're changing parameters, all the existing threads are invalid and must be removed. They will start again - // next time this Node has to process anything. - Stop(); - - // Set new parameters - width_ = width; - height_ = height; - format_ = format; - mode_ = mode; - texture_input_ = input; - - // divider's default value is 0, so we can assume if it's 0 a divider wasn't specified - if (divider > 0) { - divider_ = divider; - } - - CalculateEffectiveDimensions(); - - // Regenerate the cache ID - GenerateCacheIDInternal(); -} - -void RenderManager::SetDivider(const int ÷r) -{ - Q_ASSERT(divider_ > 0); - - Stop(); - - divider_ = divider; - - CalculateEffectiveDimensions(); - - // Regenerate the cache ID - GenerateCacheIDInternal(); -} - -void RenderManager::Start() -{ - if (started_) { - return; - } - - QOpenGLContext* ctx = QOpenGLContext::currentContext(); - - int background_thread_count = QThread::idealThreadCount(); - - // Some OpenGL implementations (notably wgl) require the context not to be current before sharing - QSurface* old_surface = ctx->surface(); - ctx->doneCurrent(); - - threads_.resize(background_thread_count); - - for (int i=0;i(this, ctx, effective_width_, effective_height_, divider_, format_, mode_); - threads_[i]->StartThread(QThread::LowPriority); - - // Ensure this connection is "Queued" so that it always runs in this object's threaded rather than any of the - // other threads - connect(threads_.at(i).get(), - SIGNAL(RequestSibling(NodeDependency)), - this, - SLOT(ThreadRequestSibling(NodeDependency)), - Qt::QueuedConnection); - } - - // Connect first thread (master thread) to the callback - connect(threads_.first().get(), - SIGNAL(CachedFrame(RenderTexturePtr, const rational&, const QByteArray&)), - this, - SLOT(ThreadCallback(RenderTexturePtr, const rational&, const QByteArray&)), - Qt::QueuedConnection); - connect(threads_.first().get(), - SIGNAL(FrameSkipped(const rational&, const QByteArray&)), - this, - SLOT(ThreadSkippedFrame(const rational&, const QByteArray&)), - Qt::QueuedConnection); - - download_threads_.resize(background_thread_count); - - for (int i=0;i(ctx, effective_width_, effective_height_, divider_, format_, mode_); - download_threads_[i]->StartThread(QThread::LowPriority); - - connect(download_threads_[i].get(), - SIGNAL(Downloaded(const QByteArray&)), - this, - SLOT(DownloadThreadComplete(const QByteArray&)), - Qt::QueuedConnection); - } - - last_download_thread_ = 0; - - // Restore context now that thread creation is complete - ctx->makeCurrent(old_surface); - - cache_frame_load_buffer_.resize(PixelService::GetBufferSize(format_, effective_width_, effective_height_)); - - started_ = true; -} - -void RenderManager::Stop() -{ - if (!started_) { - return; - } - - started_ = false; - - foreach (RendererDownloadThreadPtr download_thread_, download_threads_) { - download_thread_->Cancel(); - } - download_threads_.clear(); - - foreach (RendererProcessThreadPtr process_thread, threads_) { - process_thread->Cancel(); - } - threads_.clear(); - - cache_frame_load_buffer_.clear(); -} - -void RenderManager::GenerateCacheIDInternal() -{ - if (cache_name_.isEmpty() || effective_width_ == 0 || effective_height_ == 0) { - return; - } - - // Generate an ID that is more or less guaranteed to be unique to this Sequence - QCryptographicHash hash(QCryptographicHash::Sha1); - hash.addData(cache_name_.toUtf8()); - hash.addData(QString::number(cache_time_).toUtf8()); - hash.addData(QString::number(width_).toUtf8()); - hash.addData(QString::number(height_).toUtf8()); - hash.addData(QString::number(format_).toUtf8()); - hash.addData(QString::number(divider_).toUtf8()); - - QByteArray bytes = hash.result(); - cache_id_ = bytes.toHex(); -} - -void RenderManager::CacheNext() -{ - if (texture_input_ == nullptr || cache_queue_.isEmpty() || !texture_input_->IsConnected() || caching_) { - return; - } - - // Make sure cache has started - Start(); - - rational cache_frame = cache_queue_.takeFirst(); - - qDebug() << "Caching" << cache_frame.toDouble(); - - threads_.first()->Queue(NodeDependency(texture_input_->get_connected_output(), cache_frame), true, false); - - caching_ = true; -} - -QString RenderManager::CachePathName(const QByteArray &hash) -{ - QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id_); - this_cache_dir.mkpath("."); - - QString filename = QString("%1.exr").arg(QString(hash.toHex())); - - return this_cache_dir.filePath(filename); -} - -void RenderManager::DeferMap(const rational &time, const QByteArray &hash) -{ - deferred_maps_.append({time, hash}); -} - -bool RenderManager::HasHash(const QByteArray &hash) -{ - return QFileInfo::exists(CachePathName(hash)); -} - -bool RenderManager::IsCaching(const QByteArray &hash) -{ - cache_hash_list_mutex_.lock(); - - bool is_caching = cache_hash_list_.contains(hash); - - cache_hash_list_mutex_.unlock(); - - return is_caching; -} - -bool RenderManager::TryCache(const QByteArray &hash) -{ - cache_hash_list_mutex_.lock(); - - bool is_caching = cache_hash_list_.contains(hash); - - if (!is_caching) { - cache_hash_list_.append(hash); - } - - cache_hash_list_mutex_.unlock(); - - return !is_caching; -} - -void RenderManager::RetrieveImage(RenderTexturePtr destination_texture, const rational &time) -{ - if (cache_id_.isEmpty()) { - qWarning() << "RendererProcessor has no cache ID"; - return; - } - - if (timebase_.isNull()) { - qWarning() << "RendererProcessor has no timebase"; - return; - } - - // Find frame in map - if (time_hash_map_.contains(time)) { - QString fn = CachePathName(time_hash_map_[time]); - - if (QFileInfo::exists(fn)) { - auto in = OIIO::ImageInput::open(fn.toStdString()); - - if (in) { - in->read_image(PixelService::GetPixelFormatInfo(format_).oiio_desc, cache_frame_load_buffer_.data()); - - in->close(); - - destination_texture->Upload(cache_frame_load_buffer_.data()); - } else { - qWarning() << "OIIO Error:" << OIIO::geterror().c_str(); - } - } - } -} - -void RenderManager::CalculateEffectiveDimensions() -{ - effective_width_ = width_ / divider_; - effective_height_ = height_ / divider_; -} - -void RenderManager::ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash) -{ - // Threads are all done now, time to proceed - caching_ = false; - - DeferMap(time, hash); - - if (texture != nullptr) { - // We received a texture, time to start downloading it - QString fn = CachePathName(hash); - - download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture, - fn, - hash); - - last_download_thread_++; - } else { - // There was no texture here, we must update the viewer - DownloadThreadComplete(hash); - } - - emit FrameReadyWithTexture(time, texture); - - CacheNext(); -} - -void RenderManager::ThreadRequestSibling(NodeDependency dep) -{ - // Try to queue another thread to run this dep in advance - for (int i=1;iQueue(dep, false, true)) { - return; - } - } -} - -void RenderManager::ThreadSkippedFrame(const rational& time, const QByteArray& hash) -{ - caching_ = false; - - DeferMap(time, hash); - - if (!IsCaching(hash)) { - DownloadThreadComplete(hash); - - // Signal output to update value - emit FrameReady(time); - } - - CacheNext(); -} - -void RenderManager::DownloadThreadComplete(const QByteArray &hash) -{ - cache_hash_list_mutex_.lock(); - cache_hash_list_.removeAll(hash); - cache_hash_list_mutex_.unlock(); - - for (int i=0;i(QThread::currentThread()); -} - -RenderInstance *RenderManager::CurrentInstance() -{ - RendererThreadBase* thread = CurrentThread(); - - if (thread != nullptr) { - return thread->render_instance(); - } - - return nullptr; -} diff --git a/app/render/video/videorendermanager.h b/app/render/video/videorendermanager.h deleted file mode 100644 index f25ce2e59..000000000 --- a/app/render/video/videorendermanager.h +++ /dev/null @@ -1,218 +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 . - -***/ - -#ifndef RENDERMANAGER_H -#define RENDERMANAGER_H - -#include -#include - -#include "node/node.h" -#include "render/pixelformat.h" -#include "render/rendermodes.h" -#include "rendererdownloadthread.h" -#include "rendererprocessthread.h" - -/** - * @brief A multithreaded OpenGL based renderer for node systems - */ -class RenderManager : public QObject -{ - Q_OBJECT -public: - /** - * @brief Renderer Constructor - * - * Constructing a Renderer object will not start any threads/backend on its own. Use Start() to do this and Stop() - * when the Renderer is about to be destroyed. - */ - RenderManager(); - - static void CreateInstance(); - - static void DestroyInstance(); - - static RenderManager* instance(); - - void SetCacheName(const QString& s); - - void SetTimebase(const rational& timebase); - - void InvalidateCache(const rational &start_range, const rational &end_range, const rational &last_requested_time); - - /** - * @brief Set parameters of the Renderer - * - * The Renderer owns the buffers that are used in the rendering process and this function sets the kind of buffers - * to use. The Renderer must be stopped when calling this function. - * - * @param width - * - * Buffer width - * - * @param height - * - * Buffer height - * - * @param format - * - * Buffer pixel format - */ - void SetParameters(const int& width, - const int& height, - const olive::PixelFormat& format, - const olive::RenderMode& mode, - NodeInput *input, - const int ÷r = 0); - - void SetDivider(const int& divider); - - /** - * @brief Return whether a frame with this hash already exists - */ - bool HasHash(const QByteArray& hash); - - /** - * @brief Return whether a frame is currently being cached - */ - bool IsCaching(const QByteArray& hash); - - /** - * @brief Check if a frame is currently being cached, and if not reserve it - */ - bool TryCache(const QByteArray& hash); - - /** - * @brief Retrieve the cached frame at `time` and upload it to `destination_texture` - */ - void RetrieveImage(RenderTexturePtr destination_texture, const rational& time); - - /** - * @brief Allocate and start the multithreaded backend - */ - void Start(); - - /** - * @brief Terminate and deallocate the multithreaded backend - */ - void Stop(); - - /** - * @brief Return current instance of a RenderThread (or nullptr if there is none) - * - * This function attempts a dynamic_cast on QThread::currentThread() to RendererThread, which will return nullptr if - * the cast fails (e.g. if this function is called from the main thread rather than a RendererThread). - */ - static RendererThreadBase* CurrentThread(); - - static RenderInstance* CurrentInstance(); - -signals: - void FrameReady(rational time); - - void FrameReadyWithTexture(rational time, RenderTexturePtr texture); - -private: - static RenderManager* instance_; - - struct HashTimeMapping { - rational time; - QByteArray hash; - }; - - /** - * @brief Internal function for generating the cache ID - */ - void GenerateCacheIDInternal(); - - /** - * @brief Function called when there are frames in the queue to cache - * - * This function is NOT thread-safe and should only be called in the main thread. - */ - void CacheNext(); - - bool ShouldPushTexture(const rational &time); - - /** - * @brief Return the path of the cached image at this time - */ - QString CachePathName(const QByteArray &hash); - - void DeferMap(const rational &time, const QByteArray &hash); - - /** - * @brief Internal list of RenderProcessThreads - */ - QVector threads_; - - /** - * @brief Internal variable that contains whether the Renderer has started or not - */ - bool started_; - - int width_; - int height_; - - void CalculateEffectiveDimensions(); - - int divider_; - int effective_width_; - int effective_height_; - - olive::PixelFormat format_; - - olive::RenderMode mode_; - - rational timebase_; - double timebase_dbl_; - - QLinkedList cache_queue_; - QString cache_name_; - qint64 cache_time_; - QString cache_id_; - - bool caching_; - QVector cache_frame_load_buffer_; - - QVector download_threads_; - int last_download_thread_; - - QMap time_hash_map_; - - QMutex cache_hash_list_mutex_; - QVector cache_hash_list_; - - QList deferred_maps_; - - NodeInput* texture_input_; - -private slots: - void ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash); - - void ThreadRequestSibling(NodeDependency dep); - - void ThreadSkippedFrame(const rational &time, const QByteArray &hash); - - void DownloadThreadComplete(const QByteArray &hash); - -}; - -#endif // RENDERMANAGER_H diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 7dbe90eb8..b1e3e1282 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -53,6 +53,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeParam::kMatrix: case NodeParam::kTrack: case NodeParam::kRational: + case NodeParam::kSamples: break; case NodeParam::kInt: { @@ -198,6 +199,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeParam::kTexture: case NodeParam::kMatrix: case NodeParam::kTrack: + case NodeParam::kSamples: case NodeParam::kRational: break; case NodeParam::kInt: