From 40b38794406ddaae32187b8723d5c82280376dfc Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 15 Nov 2019 13:50:58 +0900 Subject: [PATCH] overhauled render classes to support video AND audio Major refactoring work to try sharing as much code as possible between the video renderers and audio renderers, as well as make them as platform-independent as possible. --- app/render/audioparams.cpp | 47 ++-- app/render/audioparams.h | 9 +- app/render/backend/audio/CMakeLists.txt | 2 + app/render/backend/audio/audiobackend.cpp | 54 ++++- app/render/backend/audio/audiobackend.h | 12 ++ app/render/backend/audio/audioworker.cpp | 19 +- app/render/backend/audio/audioworker.h | 15 +- app/render/backend/audiorenderbackend.cpp | 37 +++- app/render/backend/audiorenderbackend.h | 12 +- app/render/backend/audiorenderworker.cpp | 97 +++++++++ app/render/backend/audiorenderworker.h | 5 + app/render/backend/opengl/openglbackend.cpp | 39 +--- app/render/backend/opengl/openglbackend.h | 1 - app/render/backend/opengl/openglworker.cpp | 13 +- app/render/backend/opengl/openglworker.h | 6 +- app/render/backend/renderbackend.cpp | 44 +++- app/render/backend/renderbackend.h | 10 +- app/render/backend/renderworker.cpp | 182 ++++++++++++++-- app/render/backend/renderworker.h | 41 +++- app/render/backend/videorenderbackend.cpp | 25 ++- app/render/backend/videorenderbackend.h | 6 +- app/render/backend/videorenderframecache.cpp | 13 ++ app/render/backend/videorenderframecache.h | 2 + app/render/backend/videorenderworker.cpp | 216 ++++--------------- app/render/backend/videorenderworker.h | 10 - app/render/pixelservice.cpp | 10 +- 26 files changed, 628 insertions(+), 299 deletions(-) diff --git a/app/render/audioparams.cpp b/app/render/audioparams.cpp index 660d3a16c..ea04d90e3 100644 --- a/app/render/audioparams.cpp +++ b/app/render/audioparams.cpp @@ -27,23 +27,23 @@ const uint64_t &AudioParams::channel_layout() const } AudioRenderingParams::AudioRenderingParams() : - format_(olive::SAMPLE_FMT_INVALID) + format_(SAMPLE_FMT_INVALID) { } -AudioRenderingParams::AudioRenderingParams(const int &sample_rate, const uint64_t &channel_layout, const olive::SampleFormat &format) : +AudioRenderingParams::AudioRenderingParams(const int &sample_rate, const uint64_t &channel_layout, const SampleFormat &format) : AudioParams(sample_rate, channel_layout), format_(format) { } -AudioRenderingParams::AudioRenderingParams(const AudioParams ¶ms, const olive::SampleFormat &format) : +AudioRenderingParams::AudioRenderingParams(const AudioParams ¶ms, const SampleFormat &format) : AudioParams(params), format_(format) { } -const olive::SampleFormat &AudioRenderingParams::format() const +const SampleFormat &AudioRenderingParams::format() const { return format_; } @@ -52,7 +52,14 @@ int AudioRenderingParams::time_to_bytes(const rational &time) const { Q_ASSERT(is_valid()); - return qFloor(time.toDouble() * sample_rate()) * channel_count() * bytes_per_sample_per_channel(); + return time_to_samples(time) * channel_count() * bytes_per_sample_per_channel(); +} + +int AudioRenderingParams::time_to_samples(const rational &time) const +{ + Q_ASSERT(is_valid()); + + return qFloor(time.toDouble() * sample_rate()); } int AudioRenderingParams::samples_to_bytes(const int &samples) const @@ -70,18 +77,18 @@ int AudioRenderingParams::channel_count() const int AudioRenderingParams::bytes_per_sample_per_channel() const { switch (format_) { - case olive::SAMPLE_FMT_U8: + case SAMPLE_FMT_U8: return 1; - case olive::SAMPLE_FMT_S16: + case SAMPLE_FMT_S16: return 2; - case olive::SAMPLE_FMT_S32: - case olive::SAMPLE_FMT_FLT: + case SAMPLE_FMT_S32: + case SAMPLE_FMT_FLT: return 4; - case olive::SAMPLE_FMT_DBL: - case olive::SAMPLE_FMT_S64: + case SAMPLE_FMT_DBL: + case SAMPLE_FMT_S64: return 8; - case olive::SAMPLE_FMT_INVALID: - case olive::SAMPLE_FMT_COUNT: + case SAMPLE_FMT_INVALID: + case SAMPLE_FMT_COUNT: break; } @@ -95,8 +102,14 @@ int AudioRenderingParams::bits_per_sample() const bool AudioRenderingParams::is_valid() const { - return (sample_rate() > 0 - && channel_layout() > 0 - && format_ != olive::SAMPLE_FMT_INVALID - && format_ != olive::SAMPLE_FMT_COUNT); + bool valid = (sample_rate() > 0 + && channel_layout() > 0 + && format_ != SAMPLE_FMT_INVALID + && format_ != SAMPLE_FMT_COUNT); + + if (!valid) { + qWarning() << "Invalid params found:" << sample_rate() << channel_layout() << format(); + } + + return valid; } diff --git a/app/render/audioparams.h b/app/render/audioparams.h index a2fc1bbda..043f1b5ca 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -25,20 +25,21 @@ private: class AudioRenderingParams : public AudioParams { public: AudioRenderingParams(); - AudioRenderingParams(const int& sample_rate, const uint64_t& channel_layout, const olive::SampleFormat& format); - AudioRenderingParams(const AudioParams& params, const olive::SampleFormat& format); + AudioRenderingParams(const int& sample_rate, const uint64_t& channel_layout, const SampleFormat& format); + AudioRenderingParams(const AudioParams& params, const SampleFormat& format); int time_to_bytes(const rational& time) const; + int time_to_samples(const rational& time) const; int samples_to_bytes(const int& samples) const; int channel_count() const; int bytes_per_sample_per_channel() const; int bits_per_sample() const; bool is_valid() const; - const olive::SampleFormat& format() const; + const SampleFormat& format() const; private: - olive::SampleFormat format_; + SampleFormat format_; }; #endif // AUDIOPARAMS_H diff --git a/app/render/backend/audio/CMakeLists.txt b/app/render/backend/audio/CMakeLists.txt index 5fcb98547..85ec7b702 100644 --- a/app/render/backend/audio/CMakeLists.txt +++ b/app/render/backend/audio/CMakeLists.txt @@ -18,5 +18,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} render/backend/audio/audiobackend.h render/backend/audio/audiobackend.cpp + render/backend/audio/audioworker.h + render/backend/audio/audioworker.cpp PARENT_SCOPE ) diff --git a/app/render/backend/audio/audiobackend.cpp b/app/render/backend/audio/audiobackend.cpp index a19f186f8..4336ceff4 100644 --- a/app/render/backend/audio/audiobackend.cpp +++ b/app/render/backend/audio/audiobackend.cpp @@ -1,13 +1,34 @@ #include "audiobackend.h" +#include "audioworker.h" + AudioBackend::AudioBackend(QObject *parent) : AudioRenderBackend(parent) { } +AudioBackend::~AudioBackend() +{ + Close(); +} + +QIODevice *AudioBackend::GetAudioPullDevice() +{ + pull_device_.setFileName(CachePathName()); + + return &pull_device_; +} + bool AudioBackend::InitInternal() { - // This backend doesn't init anything yet + // Initiate one thread per CPU core + for (int i=0;iSetParameters(params()); + processors_.append(processor); + } + return true; } @@ -26,3 +47,34 @@ void AudioBackend::DecompileInternal() { // This backend doesn't compile anything yet } + +void AudioBackend::ConnectWorkerToThis(RenderWorker *worker) +{ + connect(worker, SIGNAL(CompletedCache(NodeDependency)), this, SLOT(ThreadCompletedCache(NodeDependency))); +} + +void AudioBackend::ThreadCompletedCache(NodeDependency dep) +{ + caching_ = false; + + QByteArray cached_samples = dep.node()->get_cached_value(dep.range()).toByteArray(); + + int offset = params().time_to_bytes(dep.in()); + int length = params().time_to_bytes(dep.range().length()); + int out_point = offset + length; + + if (pcm_data_.size() < out_point) { + pcm_data_.resize(out_point); + } + + // Replace data with this data + memcpy(pcm_data_.data() + offset, cached_samples.data(), static_cast(length)); + + QFile f(CachePathName()); + if (f.open(QFile::WriteOnly)) { + f.write(pcm_data_); + f.close(); + } + + CacheNext(); +} diff --git a/app/render/backend/audio/audiobackend.h b/app/render/backend/audio/audiobackend.h index f21a7cac8..7cbe4c428 100644 --- a/app/render/backend/audio/audiobackend.h +++ b/app/render/backend/audio/audiobackend.h @@ -1,6 +1,8 @@ #ifndef AUDIOBACKEND_H #define AUDIOBACKEND_H +#include + #include "../audiorenderbackend.h" class AudioBackend : public AudioRenderBackend @@ -9,6 +11,10 @@ class AudioBackend : public AudioRenderBackend public: AudioBackend(QObject* parent = nullptr); + virtual ~AudioBackend() override; + + virtual QIODevice* GetAudioPullDevice() override; + protected: virtual bool InitInternal() override; @@ -18,7 +24,13 @@ protected: virtual void DecompileInternal() override; + virtual void ConnectWorkerToThis(RenderWorker* worker) override; +private slots: + void ThreadCompletedCache(NodeDependency dep); + +private: + QFile pull_device_; }; diff --git a/app/render/backend/audio/audioworker.cpp b/app/render/backend/audio/audioworker.cpp index 125e249b8..b7068bc4c 100644 --- a/app/render/backend/audio/audioworker.cpp +++ b/app/render/backend/audio/audioworker.cpp @@ -1,6 +1,23 @@ #include "audioworker.h" -AudioWorker::AudioWorker() +AudioWorker::AudioWorker(DecoderCache *decoder_cache, QObject *parent) : + AudioRenderWorker(decoder_cache, parent) { +} +QVariant AudioWorker::FrameToValue(FramePtr frame) +{ + return frame->ToByteArray(); +} + +bool AudioWorker::OutputIsAccelerated(NodeOutput *output) +{ + Q_UNUSED(output) + return false; +} + +QVariant AudioWorker::RunNodeAccelerated(NodeOutput *output) +{ + Q_UNUSED(output) + return QVariant(); } diff --git a/app/render/backend/audio/audioworker.h b/app/render/backend/audio/audioworker.h index 76cea9f30..b8c42f956 100644 --- a/app/render/backend/audio/audioworker.h +++ b/app/render/backend/audio/audioworker.h @@ -1,11 +1,22 @@ #ifndef AUDIOWORKER_H #define AUDIOWORKER_H +#include "../audiorenderworker.h" -class AudioWorker +class AudioWorker : public AudioRenderWorker { public: - AudioWorker(); + AudioWorker(DecoderCache* decoder_cache, QObject* parent = nullptr); + +protected: + virtual QVariant FrameToValue(FramePtr frame) override; + + virtual bool OutputIsAccelerated(NodeOutput *output) override; + + virtual QVariant RunNodeAccelerated(NodeOutput *output) override; + +private: + }; #endif // AUDIOWORKER_H diff --git a/app/render/backend/audiorenderbackend.cpp b/app/render/backend/audiorenderbackend.cpp index 2c3fe61aa..06955f286 100644 --- a/app/render/backend/audiorenderbackend.cpp +++ b/app/render/backend/audiorenderbackend.cpp @@ -1,11 +1,13 @@ #include "audiorenderbackend.h" +#include #include +#include "common/filefunctions.h" + AudioRenderBackend::AudioRenderBackend(QObject *parent) : RenderBackend(parent) { - } void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms) @@ -23,8 +25,17 @@ void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms) void AudioRenderBackend::InvalidateCache(const rational &start_range, const rational &end_range) { + rational start_range_adj = qMax(rational(0), start_range); + rational end_range_adj = qMin(viewer_node()->Length(), end_range); + + // Truncate to length if necessary + int max_length_in_bytes = params().time_to_bytes(viewer_node()->Length()); + if (pcm_data_.size() > max_length_in_bytes) { + pcm_data_.resize(max_length_in_bytes); + } + // Add the range to the list - cache_queue_.append(TimeRange(start_range, end_range)); + cache_queue_.append(TimeRange(start_range_adj, end_range_adj)); // Remove any overlaps so we don't render the same thing twice ValidateRanges(); @@ -37,7 +48,7 @@ void AudioRenderBackend::ViewerNodeChangedEvent(ViewerOutput *node) { if (node != nullptr) { // FIXME: Hardcoded format - SetParameters(AudioRenderingParams(node->audio_params(), olive::SAMPLE_FMT_FLT)); + SetParameters(AudioRenderingParams(node->audio_params(), SAMPLE_FMT_FLT)); } } @@ -55,6 +66,16 @@ bool AudioRenderBackend::GenerateCacheIDInternal(QCryptographicHash &hash) return true; } +const AudioRenderingParams &AudioRenderBackend::params() +{ + return params_; +} + +NodeInput *AudioRenderBackend::GetDependentInput() +{ + return viewer_node()->samples_input(); +} + void AudioRenderBackend::ValidateRanges() { for (int i=0;i b.out()); + return !(a.out() < b.in() || a.in() > b.out()); } diff --git a/app/render/backend/audiorenderbackend.h b/app/render/backend/audiorenderbackend.h index e2f0d87ca..982bdbca1 100644 --- a/app/render/backend/audiorenderbackend.h +++ b/app/render/backend/audiorenderbackend.h @@ -18,6 +18,10 @@ public: */ void SetParameters(const AudioRenderingParams ¶ms); + virtual QIODevice* GetAudioPullDevice() = 0; + + const AudioRenderingParams& params(); + public slots: virtual void InvalidateCache(const rational &start_range, const rational &end_range) override; @@ -29,7 +33,11 @@ protected: */ virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) override; - //virtual void CacheIDChangedEvent(const QString& id) override; + virtual NodeInput* GetDependentInput() override; + + QString CachePathName(); + + QByteArray pcm_data_; private: void ValidateRanges(); @@ -40,8 +48,6 @@ private: AudioRenderingParams params_; - QByteArray pcm_data_; - }; #endif // AUDIORENDERBACKEND_H diff --git a/app/render/backend/audiorenderworker.cpp b/app/render/backend/audiorenderworker.cpp index 21b9934fb..403ecf73c 100644 --- a/app/render/backend/audiorenderworker.cpp +++ b/app/render/backend/audiorenderworker.cpp @@ -1,13 +1,54 @@ #include "audiorenderworker.h" +#include "audio/audiomanager.h" + AudioRenderWorker::AudioRenderWorker(DecoderCache *decoder_cache, QObject *parent) : RenderWorker(decoder_cache, parent) { } +void AudioRenderWorker::SetParameters(const AudioRenderingParams &audio_params) +{ + audio_params_ = audio_params; +} + void AudioRenderWorker::RenderAsSibling(NodeDependency dep) { + NodeOutput* output = dep.node(); + Node* node = output->parent(); + QList connected_inputs; + QVariant value; + // Set working state + working_++; + + bool locked = false; + + // Firstly we check if this node is a "Block", if it is that means it's part of a linked list of mutually exclusive + // nodes based on time and we might need to locate which Block to attach to + if (node->IsBlock() + && (dep.range().in() < static_cast(node)->in() + || dep.range().out() > static_cast(node)->out())) { + // If the range is not wholly contained in this Block, we'll need to do some extra processing + value = RenderBlock(output, dep.range()); + } else { + node->LockProcessing(); + value = ProcessNodeNormally(NodeDependency(output, dep.range())); + locked = true; + } + + if (!locked) { + node->LockProcessing(); + } + + // Place the value into the output + output->cache_value(dep.range(), value); + + // We're done! + node->UnlockProcessing(); + + // End this working state + working_--; } bool AudioRenderWorker::InitInternal() @@ -20,3 +61,59 @@ void AudioRenderWorker::CloseInternal() { // Nothing to init yet } + +QVariant AudioRenderWorker::RenderBlock(NodeOutput* output, const TimeRange &range) +{ + QList active_blocks = ValidateBlockRange(static_cast(output->parent()), range); + + // All these blocks will need to output to a buffer so we create one here + QByteArray block_range_buffer(audio_params_.time_to_bytes(range.length()), 0); + + // Loop through active blocks retrieving their audio + while (!active_blocks.isEmpty()) { + int block_for_this_thread = -1; + + for (int i=0;i(b->GetParameterWithID(output->id())); + + TimeRange range_for_block(qMax(b->in(), range.in()), + qMin(b->out(), range.out())); + + // If the block is locked, we assume another thread has it. Otherwise, we'll work with it + if (connected_output->has_cached_value(range_for_block)) { + // This output already has this value, no need to process it again + QByteArray samples_from_this_block = connected_output->get_cached_value(range_for_block).toByteArray(); + + int destination_offset = audio_params_.time_to_bytes(range_for_block.in() - range.in()); + + memcpy(block_range_buffer.data()+destination_offset, + samples_from_this_block.data(), + static_cast(samples_from_this_block.size())); + + active_blocks.removeAt(i); + i--; + } else if (!b->IsProcessingLocked()) { + if (block_for_this_thread == -1) { + block_for_this_thread = i; + } else { + emit RequestSibling(NodeDependency(connected_output, + range_for_block)); + } + } + } + + if (block_for_this_thread > -1) { + Block* b = active_blocks.at(block_for_this_thread); + TimeRange range_for_block(qMax(b->in(), range.in()), + qMin(b->out(), range.out())); + + RenderAsSibling(NodeDependency(static_cast(b->GetParameterWithID(output->id())), + range_for_block)); + } else { + QThread::msleep(500); + } + } + + return block_range_buffer; +} diff --git a/app/render/backend/audiorenderworker.h b/app/render/backend/audiorenderworker.h index 21d0e6b6f..46bb351d6 100644 --- a/app/render/backend/audiorenderworker.h +++ b/app/render/backend/audiorenderworker.h @@ -9,6 +9,8 @@ class AudioRenderWorker : public RenderWorker public: AudioRenderWorker(DecoderCache* decoder_cache, QObject* parent = nullptr); + void SetParameters(const AudioRenderingParams& audio_params); + public slots: virtual void RenderAsSibling(NodeDependency dep) override; @@ -17,7 +19,10 @@ protected: virtual void CloseInternal() override; + QVariant RenderBlock(NodeOutput *output, const TimeRange& range); +private: + AudioRenderingParams audio_params_; }; diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index 9d8ed95a0..560fa8973 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -14,7 +14,7 @@ OpenGLBackend::OpenGLBackend(QObject *parent) : OpenGLBackend::~OpenGLBackend() { - CloseInternal(); + Close(); } bool OpenGLBackend::InitInternal() @@ -32,27 +32,10 @@ bool OpenGLBackend::InitInternal() // Initiate one thread per CPU core for (int i=0;iSetParameters(params()); - - // Connect to it - connect(processor, SIGNAL(RequestSibling(NodeDependency)), this, SLOT(ThreadRequestedSibling(NodeDependency))); - connect(processor, SIGNAL(CompletedFrame(NodeDependency, QByteArray)), this, SLOT(ThreadCompletedFrame(NodeDependency, QByteArray))); - connect(processor, SIGNAL(HashAlreadyBeingCached()), this, SLOT(ThreadSkippedFrame())); - connect(processor, SIGNAL(CompletedDownload(NodeDependency, QByteArray)), this, SLOT(ThreadCompletedDownload(NodeDependency, QByteArray))); - connect(processor, SIGNAL(HashAlreadyExists(NodeDependency, QByteArray)), this, SLOT(ThreadHashAlreadyExists(NodeDependency, QByteArray))); - - // Finally, we can move it to its own thread - processor->moveToThread(thread); - - // Add processor to list processors_.append(processor); - - // This function blocks the main thread intentionally. See the documentation for this function to see why. - processor->Init(); } // Create master texture (the one sent to the viewer) @@ -77,8 +60,6 @@ void OpenGLBackend::CloseInternal() master_texture_ = nullptr; push_texture_ = nullptr; //copy_pipeline_ = nullptr; - - VideoRenderBackend::Close(); } OpenGLTexturePtr OpenGLBackend::GetCachedFrameAsTexture(const rational &time) @@ -114,7 +95,7 @@ bool OpenGLBackend::CompileInternal() bool ret = TraverseCompiling(viewer_node()); if (ret) { - qDebug() << "Compiled successfully!"; + //qDebug() << "Compiled successfully!"; compiled_ = true; } else { qDebug() << "Compile failed:" << GetError(); @@ -173,7 +154,7 @@ bool OpenGLBackend::TraverseCompiling(Node *n) shader_cache_.AddShader(connected_output, program); - qDebug() << "Compiled" << connected_output->parent()->id() << "->" << connected_output->id(); + //qDebug() << "Compiled" << connected_output->parent()->id() << "->" << connected_output->id(); } } @@ -271,20 +252,6 @@ void OpenGLBackend::ThreadCompletedFrame(NodeDependency path, QByteArray hash) CacheNext(); }*/ -void OpenGLBackend::ThreadRequestedSibling(NodeDependency dep) -{ - // Try to queue another thread to run this dep in advance - foreach (RenderWorker* worker, processors_) { - if (worker->IsAvailable()) { - QMetaObject::invokeMethod(worker, - "RenderAsSibling", - Qt::QueuedConnection, - Q_ARG(NodeDependency, dep)); - return; - } - } -} - void OpenGLBackend::ThreadCompletedDownload(NodeDependency dep, QByteArray hash) { frame_cache()->SetHash(dep.in(), hash); diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/backend/opengl/openglbackend.h index 51d414909..36e50408a 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/backend/opengl/openglbackend.h @@ -41,7 +41,6 @@ private: private slots: void ThreadCompletedFrame(NodeDependency path, QByteArray hash); - void ThreadRequestedSibling(NodeDependency dep); void ThreadCompletedDownload(NodeDependency dep, QByteArray hash); void ThreadSkippedFrame(); void ThreadHashAlreadyExists(NodeDependency dep, QByteArray hash); diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index 686c5c9ec..36a61f875 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -47,7 +47,7 @@ bool OpenGLWorker::InitInternal() return true; } -QVariant OpenGLWorker::FrameToTexture(FramePtr frame) +QVariant OpenGLWorker::FrameToValue(FramePtr frame) { OpenGLTexturePtr footage_tex = std::make_shared(); footage_tex->Create(ctx_, frame); @@ -63,7 +63,7 @@ QVariant OpenGLWorker::FrameToTexture(FramePtr frame) return QVariant::fromValue(footage_tex); } -bool OpenGLWorker::OutputIsShader(NodeOutput* output) +bool OpenGLWorker::OutputIsAccelerated(NodeOutput* output) { return shader_cache_->HasShader(output); } @@ -83,7 +83,7 @@ void OpenGLWorker::ParametersChangedEvent() } } -QVariant OpenGLWorker::RunNodeAsShader(NodeOutput *out) +QVariant OpenGLWorker::RunNodeAccelerated(NodeOutput *out) { OpenGLShaderPtr shader = shader_cache_->GetShader(out); Node* node = out->parent(); @@ -126,6 +126,7 @@ QVariant OpenGLWorker::RunNodeAsShader(NodeOutput *out) shader->setUniformValue(variable_location, input->value().value()); break; case NodeInput::kMatrix: + qDebug() << "Setting uniform value to matrix" << input->value().value(); shader->setUniformValue(variable_location, input->value().value()); break; case NodeInput::kColor: @@ -138,14 +139,14 @@ QVariant OpenGLWorker::RunNodeAsShader(NodeOutput *out) case NodeInput::kFootage: { OpenGLTexturePtr texture = input->value().value(); - qDebug() << " Binding" << texture->texture() << "from" << input << "to GL_TEXTURE" << input_texture_count; + //qDebug() << " Binding" << texture->texture() << "from" << input << "to GL_TEXTURE" << input_texture_count; functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count); functions_->glBindTexture(GL_TEXTURE_2D, texture->texture()); // Set value to bound texture shader->setUniformValue(variable_location, input_texture_count); - qDebug() << " Setting" << input->id() << "to" << input_texture_count; + //qDebug() << " Setting" << input->id() << "to" << input_texture_count; input_texture_count++; break; @@ -165,7 +166,7 @@ QVariant OpenGLWorker::RunNodeAsShader(NodeOutput *out) } } - qDebug() << " Blitting with shader!"; + //qDebug() << " Blitting with shader!"; olive::gl::Blit(shader); // Release any textures we bound before diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h index 2702f61c3..556efb6da 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/backend/opengl/openglworker.h @@ -45,11 +45,11 @@ protected: virtual void CloseInternal() override; - virtual QVariant FrameToTexture(FramePtr frame) override; + virtual QVariant FrameToValue(FramePtr frame) override; - virtual bool OutputIsShader(NodeOutput *output) override; + virtual bool OutputIsAccelerated(NodeOutput *output) override; - virtual QVariant RunNodeAsShader(NodeOutput *output) override; + virtual QVariant RunNodeAccelerated(NodeOutput *output) override; virtual void TextureToBuffer(const QVariant& texture, QByteArray& buffer) override; diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 12af1eb5c..9efbd62a3 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -10,11 +10,6 @@ RenderBackend::RenderBackend(QObject *parent) : started_(false), viewer_node_(nullptr) { - -} - -RenderBackend::~RenderBackend() -{ } bool RenderBackend::Init() @@ -35,6 +30,9 @@ bool RenderBackend::Init() started_ = InitInternal(); + // Connects workers and moves them to their respective threads + InitWorkers(); + if (!started_) { Close(); } @@ -163,7 +161,7 @@ void RenderBackend::CacheNext() TimeRange cache_frame = cache_queue_.takeFirst(); - qDebug() << "Caching FRAME" << cache_frame.in() << "to" << cache_frame.out(); + //qDebug() << "Caching FRAME" << cache_frame.in() << "to" << cache_frame.out(); caching_ = GenerateData(cache_frame); } @@ -175,7 +173,7 @@ bool RenderBackend::GenerateData(const TimeRange &range) return false; } - NodeDependency dep = NodeDependency(viewer_node()->texture_input()->get_connected_output(), range.in(), range.out()); + NodeDependency dep = NodeDependency(GetDependentInput()->get_connected_output(), range.in(), range.out()); foreach (RenderWorker* worker, processors_) { if (worker->IsAvailable() || worker == processors_.last()) { @@ -214,3 +212,35 @@ void RenderBackend::CacheIDChangedEvent(const QString &id) { Q_UNUSED(id) } + +void RenderBackend::InitWorkers() +{ + for (int i=0;imoveToThread(thread); + + // This function blocks the main thread intentionally. See the documentation for this function to see why. + processor->Init(); + } +} + +void RenderBackend::ThreadRequestedSibling(NodeDependency dep) +{ + // Try to queue another thread to run this dep in advance + foreach (RenderWorker* worker, processors_) { + if (worker->IsAvailable()) { + QMetaObject::invokeMethod(worker, + "RenderAsSibling", + Qt::QueuedConnection, + Q_ARG(NodeDependency, dep)); + return; + } + } +} diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index d4c8467fa..6d928c10d 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -12,7 +12,6 @@ class RenderBackend : public QObject Q_OBJECT public: RenderBackend(QObject* parent = nullptr); - virtual ~RenderBackend() override; Q_DISABLE_COPY_MOVE(RenderBackend) @@ -68,6 +67,12 @@ protected: bool GenerateData(const TimeRange& range); + void InitWorkers(); + + virtual NodeInput* GetDependentInput() = 0; + + virtual void ConnectWorkerToThis(RenderWorker* worker) = 0; + ViewerOutput* viewer_node() const; DecoderCache* decoder_cache(); @@ -109,6 +114,9 @@ private: qint64 cache_time_; QString cache_id_; +private slots: + void ThreadRequestedSibling(NodeDependency dep); + }; #endif // RENDERBACKEND_H diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 19d600f3e..c24d86cfb 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -1,5 +1,7 @@ #include "renderworker.h" +#include + #include "node/block/block.h" RenderWorker::RenderWorker(DecoderCache *decoder_cache, QObject *parent) : @@ -50,6 +52,8 @@ void RenderWorker::Render(NodeDependency path) RenderInternal(path); + emit CompletedCache(path); + // Unlock all Nodes so changes can be made again foreach (Node* dep, all_nodes_in_graph) { dep->UnlockUserInput(); @@ -61,26 +65,47 @@ DecoderCache *RenderWorker::decoder_cache() return decoder_cache_; } -Node *RenderWorker::ValidateBlock(Node *n, const rational& time) +Block *RenderWorker::ValidateBlock(Block *block, const rational& time) { - if (n->IsBlock()) { - Block* block = static_cast(n); + Q_ASSERT(block != nullptr && time >= 0); - while (block != nullptr && block->in() > time) { - // This Block is too late, find an earlier one - block = block->previous(); - } - - while (block != nullptr && block->out() <= time) { - // This block is too early, find a later one - block = block->next(); - } - - // By this point, we should have the correct Block or nullptr if there's no Block here - return block; + while (block->in() > time) { + // This Block is too late, find an earlier one + block = block->previous(); } - return n; + while (block->out() <= time) { + // This block is too early, find a later one + if (block->next() == nullptr) { + break; + } + + block = block->next(); + } + + // By this point, we should have the correct Block or nullptr if there's no Block here + return block; +} + +QList RenderWorker::ValidateBlockRange(Block *n, const TimeRange &range) +{ + QList list; + Block* block_at_start = ValidateBlock(n, range.in()); + Block* block_at_end = ValidateBlock(n, range.out()); + + list.append(block_at_start); + + // If more than one block is active for this range + if (block_at_start != block_at_end) { + + // Collect all blocks between the start and the end + do { + block_at_start = block_at_start->next(); + list.append(block_at_start); + } while (block_at_start != block_at_end); + } + + return list; } void RenderWorker::RenderInternal(const NodeDependency &path) @@ -123,3 +148,128 @@ bool RenderWorker::IsStarted() { return started_; } + +QList RenderWorker::ProcessNodeInputsForTime(Node *n, const TimeRange &time) +{ + QList connected_inputs; + + // Now we need to gather information about this Node's inputs + foreach (NodeParam* param, n->parameters()) { + // Check if this parameter is an input and if the Node is dependent on it + if (param->type() == NodeParam::kInput) { + NodeInput* input = static_cast(param); + + if (input->dependent()) { + // If we're here, this input is necessary and we need to acquire the value for this Node + if (input->IsConnected()) { + // If it's connected to something, we need to retrieve that output at some point + connected_inputs.append(input); + } else { + // If it isn't connected, it'll have the value we need inside it. We just need to store it for the node. + input->set_stored_value(input->get_value_at_time(n->InputTimeAdjustment(input, time).in())); + } + + // Special types like FOOTAGE require extra work from us (to decrease node complexity dealing with decoders) + if (input->data_type() == NodeParam::kFootage) { + input->set_stored_value(0); + + DecoderPtr decoder = ResolveDecoderFromInput(input); + + // By this point we should definitely have a decoder, and if we don't something's gone terribly wrong + if (decoder != nullptr) { + FramePtr frame = decoder->Retrieve(time.in(), time.out() - time.in()); + + if (frame != nullptr) { + QVariant value = FrameToValue(frame); + + input->set_stored_value(value); + } + } + } + } + } + } + + return connected_inputs; +} + +QVariant RenderWorker::ProcessNodeNormally(const NodeDependency& dep) +{ + NodeOutput* output = dep.node(); + Node* node = dep.node()->parent(); + + //qDebug() << "Processing" << node->id(); + + // Check if the output already has a value for this time + if (output->has_cached_value(dep.range())) { + // If so, we don't need to do anything, we can just send this value and exit here + return output->get_cached_value(dep.range()); + } + + // We need to run the Node's code to get the correct value for this time + + QList connected_inputs = ProcessNodeInputsForTime(node, dep.range()); + + // For each connected input, we need to acquire the value from another node + while (!connected_inputs.isEmpty()) { + + // Remove any inputs from the list that we have valid cached values for already + for (int i=0;iget_connected_output(); + TimeRange input_time = node->InputTimeAdjustment(input, dep.range()); + + if (connected_output->has_cached_value(input_time)) { + // This output already has this value, no need to process it again + input->set_stored_value(connected_output->get_cached_value(input_time)); + connected_inputs.removeAt(i); + i--; + } + } + + // For every connected input except the first, we'll request another Node to do it + int input_for_this_thread = -1; + + for (int i=0;iget_connected_node()->IsProcessingLocked()) { + if (input_for_this_thread == -1) { + // Store this later since we can process it on this thread as we wait for other threads + input_for_this_thread = i; + } else { + TimeRange input_time = node->InputTimeAdjustment(input, dep.range()); + + emit RequestSibling(NodeDependency(input->get_connected_output(), + input_time)); + } + } + } + + if (input_for_this_thread > -1) { + // In the mean time, this thread can go off to do the first parameter + NodeInput* input = connected_inputs.at(input_for_this_thread); + TimeRange input_range = node->InputTimeAdjustment(input, dep.range()); + RenderAsSibling(NodeDependency(input->get_connected_output(), + input_range)); + input->set_stored_value(input->get_connected_output()->get_cached_value(input_range)); + connected_inputs.removeAt(input_for_this_thread); + } else { + // Nothing for this thread to do. We'll wait 0.5 sec and check again for other nodes + // FIXME: It would be nicer if this thread could do other nodes during this time + QThread::msleep(500); + } + } + + // By this point, the node should have all the inputs it needs to render correctly + + // Check if we have a shader for this output + if (OutputIsAccelerated(output)) { + // Run code + return RunNodeAccelerated(output); + } else { + // Generate the value as expected + return node->Value(output); + } +} diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index f2a30044b..134012363 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -3,6 +3,7 @@ #include +#include "node/block/block.h" #include "node/node.h" #include "decodercache.h" @@ -27,8 +28,36 @@ public slots: virtual void RenderAsSibling(NodeDependency dep) = 0; +signals: + void RequestSibling(NodeDependency path); + + void CompletedCache(NodeDependency dep); + protected: - Node* ValidateBlock(Node* n, const rational& time); + /** + * @brief Returns the block in a sequence that is active at a given time + * + * Blocks are connected to each other previous/next to create a BlockList or a sequence of blocks. The block that + * is currently "active" depends on the time and only one block in a track can be active at any given time. + * + * Calling this function with a block and a time will traverse the provided block's track to find the block that will + * be active at that time. The block must be valid (non-null) and the time must be valid (>= 0). + * + * This function may return the same block that it was called with. It will never return nullptr. + */ + Block *ValidateBlock(Block* block, const rational& time); + + /** + * @brief Returns all the blocks that could be active within a range of time + * + * Similar to ValidateBlock() but rather than returning one block for a single time, this function returns a list of + * blocks that could be active within a range of time. + * + * The block must be valid (non-null) and the time must be valid (>= 0). + * + * The list will always contain at least one entry. + */ + QList ValidateBlockRange(Block* n, const TimeRange& range); virtual bool InitInternal() = 0; @@ -36,11 +65,21 @@ protected: virtual void RenderInternal(const NodeDependency& path); + virtual bool OutputIsAccelerated(NodeOutput *output) = 0; + + virtual QVariant RunNodeAccelerated(NodeOutput *output) = 0; + StreamPtr ResolveStreamFromInput(NodeInput* input); DecoderPtr ResolveDecoderFromInput(NodeInput* input); QList ListNodeAndAllDependencies(Node* n); + QList ProcessNodeInputsForTime(Node* n, const TimeRange& time); + + virtual QVariant FrameToValue(FramePtr frame) = 0; + + QVariant ProcessNodeNormally(const NodeDependency &dep); + DecoderCache* decoder_cache(); QAtomicInt working_; diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 253593074..905e1a970 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -36,11 +36,6 @@ VideoRenderBackend::VideoRenderBackend(QObject *parent) : SetCacheName("Test"); } -VideoRenderBackend::~VideoRenderBackend() -{ - Close(); -} - void VideoRenderBackend::InvalidateCache(const rational &start_range, const rational &end_range) { if (!params_.is_valid()) { @@ -51,10 +46,10 @@ void VideoRenderBackend::InvalidateCache(const rational &start_range, const rati rational start_range_adj = qMax(rational(0), start_range); rational end_range_adj = qMin(viewer_node()->Length(), end_range); - qDebug() << "Cache invalidated between" + /*qDebug() << "Cache invalidated between" << start_range_adj.toDouble() << "and" - << end_range_adj.toDouble(); + << end_range_adj.toDouble();*/ // Snap start_range to timebase double start_range_dbl = start_range_adj.toDouble(); @@ -100,6 +95,9 @@ void VideoRenderBackend::InvalidateCache(const rational &start_range, const rati } } + // Remove frames after this time code if it's changed + frame_cache_.Truncate(viewer_node()->Length()); + CacheNext(); } @@ -160,6 +158,14 @@ void VideoRenderBackend::CacheIDChangedEvent(const QString &id) frame_cache_.SetCacheID(id); } +void VideoRenderBackend::ConnectWorkerToThis(RenderWorker *processor) +{ + connect(processor, SIGNAL(CompletedFrame(NodeDependency, QByteArray)), this, SLOT(ThreadCompletedFrame(NodeDependency, QByteArray))); + connect(processor, SIGNAL(HashAlreadyBeingCached()), this, SLOT(ThreadSkippedFrame())); + connect(processor, SIGNAL(CompletedDownload(NodeDependency, QByteArray)), this, SLOT(ThreadCompletedDownload(NodeDependency, QByteArray))); + connect(processor, SIGNAL(HashAlreadyExists(NodeDependency, QByteArray)), this, SLOT(ThreadHashAlreadyExists(NodeDependency, QByteArray))); +} + VideoRenderFrameCache *VideoRenderBackend::frame_cache() { return &frame_cache_; @@ -207,3 +213,8 @@ const char *VideoRenderBackend::GetCachedFrame(const rational &time) return nullptr; } + +NodeInput *VideoRenderBackend::GetDependentInput() +{ + return viewer_node()->texture_input(); +} diff --git a/app/render/backend/videorenderbackend.h b/app/render/backend/videorenderbackend.h index 02c4e558c..5d61273e7 100644 --- a/app/render/backend/videorenderbackend.h +++ b/app/render/backend/videorenderbackend.h @@ -44,8 +44,6 @@ public: */ VideoRenderBackend(QObject* parent = nullptr); - virtual ~VideoRenderBackend() override; - /** * @brief Set parameters of the Renderer * @@ -77,6 +75,8 @@ protected: const char *GetCachedFrame(const rational& time); + virtual NodeInput* GetDependentInput() override; + VideoRenderFrameCache* frame_cache(); const VideoRenderingParams& params() const; @@ -88,6 +88,8 @@ protected: virtual void CacheIDChangedEvent(const QString& id) override; + virtual void ConnectWorkerToThis(RenderWorker* processor) override; + signals: void CachedFrameReady(const rational& time); diff --git a/app/render/backend/videorenderframecache.cpp b/app/render/backend/videorenderframecache.cpp index 5d6b24c1a..04ea96dd9 100644 --- a/app/render/backend/videorenderframecache.cpp +++ b/app/render/backend/videorenderframecache.cpp @@ -67,6 +67,19 @@ void VideoRenderFrameCache::RemoveHash(const rational &time) time_hash_map_.remove(time); } +void VideoRenderFrameCache::Truncate(const rational &time) +{ + QMap::iterator i = time_hash_map_.begin(); + + while (i != time_hash_map_.end()) { + if (i.key() >= time) { + i = time_hash_map_.erase(i); + } else { + i++; + } + } +} + void VideoRenderFrameCache::RemoveHashFromCurrentlyCaching(const QByteArray &hash) { currently_caching_lock_.lock(); diff --git a/app/render/backend/videorenderframecache.h b/app/render/backend/videorenderframecache.h index 4f6fbbda0..a96695632 100644 --- a/app/render/backend/videorenderframecache.h +++ b/app/render/backend/videorenderframecache.h @@ -37,6 +37,8 @@ public: void SetHash(const rational& time, const QByteArray& hash); void RemoveHash(const rational& time); + void Truncate(const rational& time); + private: void RemoveHashFromCurrentlyCaching(const QByteArray& hash); diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index 2df775198..e5fb3ddba 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -1,7 +1,5 @@ #include "videorenderworker.h" -#include - #include "common/define.h" #include "node/node.h" #include "render/pixelservice.h" @@ -43,8 +41,8 @@ void VideoRenderWorker::RenderInternal(const NodeDependency& path) void VideoRenderWorker::HashNodeRecursively(QCryptographicHash *hash, Node* n, const rational& time) { // Resolve BlockList - if (n->IsBlock() && (n = ValidateBlock(n, time)) == nullptr) { - return; + if (n->IsBlock()) { + n = ValidateBlock(static_cast(n), time); } // Add this Node's ID @@ -55,42 +53,44 @@ void VideoRenderWorker::HashNodeRecursively(QCryptographicHash *hash, Node* n, c if (param->type() == NodeParam::kInput) { NodeInput* input = static_cast(param); - // Get time adjustment - TimeRange range = n->InputTimeAdjustment(input, TimeRange(time, time)); + if (input->dependent()) { + // Get time adjustment + TimeRange range = n->InputTimeAdjustment(input, TimeRange(time, time)); - // For a single frame, we only care about one of the times - rational input_time = range.in(); + // For a single frame, we only care about one of the times + rational input_time = range.in(); - if (input->IsConnected()) { - // Traverse down this edge - HashNodeRecursively(hash, input->get_connected_node(), input_time); - } else { - // Grab the value at this time - QVariant value = input->get_value_at_time(input_time); - hash->addData(NodeParam::ValueToBytes(input->data_type(), value)); - } + if (input->IsConnected()) { + // Traverse down this edge + HashNodeRecursively(hash, input->get_connected_node(), input_time); + } else { + // Grab the value at this time + QVariant value = input->get_value_at_time(input_time); + hash->addData(NodeParam::ValueToBytes(input->data_type(), value)); + } - // We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer - if (input->data_type() == NodeParam::kFootage) { - StreamPtr stream = ResolveStreamFromInput(input); - DecoderPtr decoder = ResolveDecoderFromInput(input); + // We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer + if (input->data_type() == NodeParam::kFootage) { + StreamPtr stream = ResolveStreamFromInput(input); + DecoderPtr decoder = ResolveDecoderFromInput(input); - if (decoder != nullptr) { - // Add footage details to hash + if (decoder != nullptr) { + // Add footage details to hash - // Footage filename - hash->addData(stream->footage()->filename().toUtf8()); + // Footage filename + hash->addData(stream->footage()->filename().toUtf8()); - // Footage last modified date - hash->addData(stream->footage()->timestamp().toString().toUtf8()); + // Footage last modified date + hash->addData(stream->footage()->timestamp().toString().toUtf8()); - // Footage stream - hash->addData(QString::number(stream->index()).toUtf8()); + // Footage stream + hash->addData(QString::number(stream->index()).toUtf8()); - // Footage timestamp - hash->addData(QString::number(decoder->GetTimestampFromTime(time)).toUtf8()); + // Footage timestamp + hash->addData(QString::number(decoder->GetTimestampFromTime(time)).toUtf8()); - // FIXME: Add colorspace and alpha assoc + // FIXME: Add colorspace and alpha assoc + } } } } @@ -115,165 +115,40 @@ void VideoRenderWorker::CloseInternal() download_buffer_.clear(); } -QList VideoRenderWorker::ProcessNodeInputsForTime(Node *n, const TimeRange &time) -{ - QList connected_inputs; - - // Now we need to gather information about this Node's inputs - foreach (NodeParam* param, n->parameters()) { - // Check if this parameter is an input and if the Node is dependent on it - if (param->type() == NodeParam::kInput) { - NodeInput* input = static_cast(param); - - if (input->dependent()) { - // If we're here, this input is necessary and we need to acquire the value for this Node - if (input->IsConnected()) { - // If it's connected to something, we need to retrieve that output at some point - connected_inputs.append(input); - } else { - // If it isn't connected, it'll have the value we need inside it. We just need to store it for the node. - input->set_stored_value(input->get_value_at_time(n->InputTimeAdjustment(input, time).in())); - } - - // Special types like FOOTAGE require extra work from us (to decrease node complexity dealing with decoders) - if (input->data_type() == NodeParam::kFootage) { - input->set_stored_value(0); - - DecoderPtr decoder = ResolveDecoderFromInput(input); - - // By this point we should definitely have a decoder, and if we don't something's gone terribly wrong - if (decoder != nullptr) { - FramePtr frame = decoder->Retrieve(time.in()); - - if (frame != nullptr) { - QVariant value = FrameToTexture(frame); - - input->set_stored_value(value); - } - } - } - } - } - } - - return connected_inputs; -} - void VideoRenderWorker::RenderAsSibling(NodeDependency dep) { NodeOutput* output = dep.node(); Node* original_node = output->parent(); Node* node; rational time = dep.in(); - QList connected_inputs; QVariant value; // Set working state working_++; - qDebug() << "Processing" << original_node->id() << original_node; + //qDebug() << "Processing" << original_node->id() << original_node; original_node->LockProcessing(); // Firstly we check if this node is a "Block", if it is that means it's part of a linked list of mutually exclusive // nodes based on time and we might need to locate which Block to attach to - if ((node = ValidateBlock(original_node, time)) == nullptr) { - // ValidateBlock() may have returned nullptr if there was no Block found at this time so no texture to return - output->cache_value(dep.range(), 0); + if (original_node->IsBlock()) { + node = ValidateBlock(static_cast(original_node), time); - original_node->UnlockProcessing(); - goto end_render; - } + if (original_node != node) { + // Ensure output is the output matching the node as it may have changed + output = static_cast(node->GetParameterWithID(output->id())); - if (original_node != node) { - // Ensure output is the output matching the node as it may have changed - output = static_cast(node->GetParameterWithID(output->id())); - - // Switch locks - original_node->UnlockProcessing(); - node->LockProcessing(); - - qDebug() << "Deftly switched from" << original_node->id() << original_node << "to" << node->id() << node; - } - - // Check if the output already has a value for this time - if (output->has_cached_value(dep.range())) { - // If so, we don't need to do anything, we can just send this value and exit here - dep.node()->cache_value(dep.range(), output->get_cached_value(dep.range())); - - qDebug() << "Found a cached value on" << node->id() << output->id() << "at" << dep.range().in() << "-" << dep.range().out(); - - node->UnlockProcessing(); - goto end_render; - } - - // We need to run the Node's code to get the correct value for this time - - connected_inputs = ProcessNodeInputsForTime(node, dep.range()); - - // For each connected input, we need to acquire the value from another node - while (!connected_inputs.isEmpty()) { - - // Remove any inputs from the list that we have valid cached values for already - for (int i=0;iget_connected_output(); - TimeRange input_time = node->InputTimeAdjustment(input, dep.range()); - - if (connected_output->has_cached_value(input_time)) { - // This output already has this value, no need to process it again - input->set_stored_value(connected_output->get_cached_value(input_time)); - connected_inputs.removeAt(i); - i--; - } + // Switch locks + original_node->UnlockProcessing(); + node->LockProcessing(); } - - // For every connected input except the first, we'll request another Node to do it - int input_for_this_thread = -1; - - for (int i=0;iget_connected_node()->IsProcessingLocked()) { - if (input_for_this_thread == -1) { - // Store this later since we can process it on this thread as we wait for other threads - input_for_this_thread = i; - } else { - TimeRange input_time = node->InputTimeAdjustment(input, dep.range()); - - emit RequestSibling(NodeDependency(input->get_connected_output(), - input_time)); - } - } - } - - if (input_for_this_thread > -1) { - // In the mean time, this thread can go off to do the first parameter - NodeInput* input = connected_inputs.at(input_for_this_thread); - TimeRange input_range = node->InputTimeAdjustment(input, dep.range()); - RenderAsSibling(NodeDependency(input->get_connected_output(), - input_range)); - input->set_stored_value(input->get_connected_output()->get_cached_value(input_range)); - connected_inputs.removeAt(input_for_this_thread); - } else { - // Nothing for this thread to do. We'll wait 0.5 sec and check again for other nodes - // FIXME: It would be nicer if this thread could do other nodes during this time - QThread::msleep(500); - } - } - - // By this point, the node should have all the inputs it needs to render correctly - - // Check if we have a shader for this output - if (OutputIsShader(output)) { - // Run code - value = RunNodeAsShader(output); } else { - // Generate the value as expected - value = node->Value(output); + node = original_node; } + value = ProcessNodeNormally(NodeDependency(output, dep.range())); + // Place the value into the output output->cache_value(dep.range(), value); dep.node()->cache_value(dep.range(), value); @@ -281,13 +156,10 @@ void VideoRenderWorker::RenderAsSibling(NodeDependency dep) // We're done! node->UnlockProcessing(); -end_render: // End this working state working_--; } - - void VideoRenderWorker::Download(NodeDependency dep, QByteArray hash, QVariant texture, QString filename) { working_++; @@ -305,7 +177,7 @@ void VideoRenderWorker::Download(NodeDependency dep, QByteArray hash, QVariant t std::unique_ptr out = OIIO::ImageOutput::create(working_fn_std); if (out) { - qDebug() << "Saving to" << filename; + //qDebug() << "Saving to" << filename; out->open(working_fn_std, spec); out->write_image(format_info.oiio_desc, download_buffer_.data()); out->close(); diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h index 682c8bc4b..9f370ba52 100644 --- a/app/render/backend/videorenderworker.h +++ b/app/render/backend/videorenderworker.h @@ -21,8 +21,6 @@ public slots: void Download(NodeDependency dep, QByteArray hash, QVariant texture, QString filename); signals: - void RequestSibling(NodeDependency path); - void CompletedFrame(NodeDependency path, QByteArray hash); void CompletedDownload(NodeDependency path, QByteArray hash); @@ -36,16 +34,10 @@ protected: virtual void CloseInternal() override; - virtual QVariant FrameToTexture(FramePtr frame) = 0; - const VideoRenderingParams& video_params(); virtual void ParametersChangedEvent(){} - virtual bool OutputIsShader(NodeOutput *output) = 0; - - virtual QVariant RunNodeAsShader(NodeOutput *output) = 0; - virtual void TextureToBuffer(const QVariant& texture, QByteArray& buffer) = 0; virtual void RenderInternal(const NodeDependency& path) override; @@ -53,8 +45,6 @@ protected: private: void ProcessNode(); - QList ProcessNodeInputsForTime(Node* n, const TimeRange& time); - void HashNodeRecursively(QCryptographicHash* hash, Node *n, const rational &time); VideoRenderingParams video_params_; diff --git a/app/render/pixelservice.cpp b/app/render/pixelservice.cpp index 6ca6916ae..7a90843ba 100644 --- a/app/render/pixelservice.cpp +++ b/app/render/pixelservice.cpp @@ -122,7 +122,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm switch (static_cast(frame->format())) { case olive::PIX_FMT_RGBA8: { - uint8_t* source = frame->data(); + uint8_t* source = reinterpret_cast(frame->data()); switch (dest_format) { case olive::PIX_FMT_RGBA16U: // 8-bit Integer -> 16-bit Integer @@ -163,7 +163,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm switch (dest_format) { case olive::PIX_FMT_RGBA8: // 16-bit Integer -> 8-bit Integer { - uint8_t* destination = converted->data(); + uint8_t* destination = reinterpret_cast(converted->data()); for (int i=0;i(source[i] / 257); } @@ -199,7 +199,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm switch (dest_format) { case olive::PIX_FMT_RGBA8: // 16-bit Float -> 8-bit Integer { - uint8_t* destination = converted->data(); + uint8_t* destination = reinterpret_cast(converted->data()); for (int i=0;i(source[i] * 255.0f); } @@ -235,7 +235,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm switch (dest_format) { case olive::PIX_FMT_RGBA8: // 32-bit Float -> 8-bit Integer { - uint8_t* destination = converted->data(); + uint8_t* destination = reinterpret_cast(converted->data()); for (int i=0;i(source[i] * 255.0f); } @@ -293,7 +293,7 @@ void PixelService::ConvertRGBtoRGBA(FramePtr frame) while (rgb_iter >= 0) { memcpy(&frame->data()[rgba_iter], &frame->data()[rgb_iter], static_cast(rgb_pixel_size)); - uint8_t* alpha_ptr = &frame->data()[rgba_iter + rgb_pixel_size]; + uint8_t* alpha_ptr = reinterpret_cast(frame->data()) + rgba_iter + rgb_pixel_size; // Write a full alpha value according to the format switch (dest_format) {