From 663ae56ac4380ed71646f5f0b5a724ad2ea7f5c8 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 3 Nov 2019 04:05:43 +1100 Subject: [PATCH] more coherent render threading for now Once again, conceptually this system should work, however it does not seem to be the most efficient and it wouldn't surprise me if the multithreading was eventually upgraded to an even more coherent system one day. However for "core principles" this should be fairly decent. --- app/common/timerange.cpp | 24 +- app/common/timerange.h | 4 - app/decoder/ffmpeg/ffmpegdecoder.cpp | 71 ++--- app/node/node.cpp | 21 ++ app/node/node.h | 6 + app/render/backend/CMakeLists.txt | 3 + app/render/backend/opengl/CMakeLists.txt | 4 + app/render/backend/opengl/decodercache.cpp | 15 + app/render/backend/opengl/decodercache.h | 15 + app/render/backend/opengl/openglbackend.cpp | 27 +- app/render/backend/opengl/openglbackend.h | 16 +- .../backend/opengl/openglshadercache.cpp | 23 ++ app/render/backend/opengl/openglshadercache.h | 19 ++ app/render/backend/opengl/opengltexture.cpp | 10 + app/render/backend/opengl/opengltexture.h | 3 + app/render/backend/opengl/openglworker.cpp | 277 +++++++++++++++++- app/render/backend/opengl/openglworker.h | 15 +- app/render/backend/videorenderbackend.cpp | 1 + 18 files changed, 447 insertions(+), 107 deletions(-) diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index 21f30578e..942a826a1 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -47,32 +47,12 @@ void TimeRange::set_range(const rational &in, const rational &out) bool TimeRange::operator<(const TimeRange &r) const { - return length() < r.length(); -} - -bool TimeRange::operator<=(const TimeRange &r) const -{ - return length() <= r.length(); + return (in() + out()) < (r.in() + r.out()); } bool TimeRange::operator>(const TimeRange &r) const { - return length() > r.length(); -} - -bool TimeRange::operator>=(const TimeRange &r) const -{ - return length() >= r.length(); -} - -bool TimeRange::operator==(const TimeRange &r) const -{ - return length() == r.length(); -} - -bool TimeRange::operator!=(const TimeRange &r) const -{ - return length() != r.length(); + return (in() + out()) > (r.in() + r.out()); } void TimeRange::normalize() diff --git a/app/common/timerange.h b/app/common/timerange.h index 7ca30956f..24b36e0fc 100644 --- a/app/common/timerange.h +++ b/app/common/timerange.h @@ -17,11 +17,7 @@ public: void set_range(const rational& in, const rational& out); bool operator<(const TimeRange &r) const; - bool operator<=(const TimeRange &r) const; bool operator>(const TimeRange &r) const; - bool operator>=(const TimeRange &r) const; - bool operator==(const TimeRange &r) const; - bool operator!=(const TimeRange &r) const; private: void normalize(); diff --git a/app/decoder/ffmpeg/ffmpegdecoder.cpp b/app/decoder/ffmpeg/ffmpegdecoder.cpp index fc3a6f679..bb09a973b 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.cpp +++ b/app/decoder/ffmpeg/ffmpegdecoder.cpp @@ -203,49 +203,52 @@ FramePtr FFmpegDecoder::Retrieve(const rational &timecode, const rational &lengt return nullptr; } - // Cache FFmpeg error code returns - int ret = 0; + // Check if this is already the frame we have cached + if (frame_->pts != target_ts) { + // Cache FFmpeg error code returns + int ret = 0; - // Set up seeking loop - int64_t seek_ts = target_ts; - int64_t second_ts = qRound(rational(avstream_->time_base).flipped().toDouble()); - bool got_frame = false; - bool last_backtrack = false; + // Set up seeking loop + int64_t seek_ts = target_ts; + int64_t second_ts = qRound(rational(avstream_->time_base).flipped().toDouble()); + bool got_frame = false; + bool last_backtrack = false; - // FFmpeg frame retrieve loop - while (ret >= 0 && frame_->pts != target_ts) { + // FFmpeg frame retrieve loop + while (ret >= 0 && frame_->pts != target_ts) { - // If the frame timestamp is too large, we need to seek back a little - if (got_frame && (frame_->pts > target_ts || frame_->pts == AV_NOPTS_VALUE)) { - // If we already tried seeking to 0 though, there's nothing we can do so we error here - if (last_backtrack) { - // Must be the earliest frame in the file - break; + // If the frame timestamp is too large, we need to seek back a little + if (got_frame && (frame_->pts > target_ts || frame_->pts == AV_NOPTS_VALUE)) { + // If we already tried seeking to 0 though, there's nothing we can do so we error here + if (last_backtrack) { + // Must be the earliest frame in the file + break; + } + + // We can't seek earlier than 0, so if this is a 0-seek, don't try any more times after this attempt + if (seek_ts <= 0) { + seek_ts = 0; + last_backtrack = true; + } + + Seek(seek_ts); + + // FFmpeg doesn't always seek correctly, if we have to seek again we wrangle it into seeking back far enough + seek_ts -= second_ts; } - // We can't seek earlier than 0, so if this is a 0-seek, don't try any more times after this attempt - if (seek_ts <= 0) { - seek_ts = 0; - last_backtrack = true; - } - - Seek(seek_ts); - - // FFmpeg doesn't always seek correctly, if we have to seek again we wrangle it into seeking back far enough - seek_ts -= second_ts; + ret = GetFrame(); + got_frame = true; } - ret = GetFrame(); - got_frame = true; + // Handle any errors received during the frame retrieve process + if (ret < 0) { + FFmpegError(ret); + return nullptr; + } } - // Handle any errors received during the frame retrieve process - if (ret < 0) { - FFmpegError(ret); - return nullptr; - } - - // Frame was valid, now we create an Olive frame to place the data into + // Frame was valid, now we convert it to a native Olive frame FramePtr frame_container = Frame::Create(); frame_container->set_width(frame_->width); frame_container->set_height(frame_->height); diff --git a/app/node/node.cpp b/app/node/node.cpp index dde64f042..9836840cf 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -135,6 +135,16 @@ void Node::UnlockProcessing() processing_lock_.unlock(); } +bool Node::IsProcessingLocked() +{ + if (processing_lock_.tryLock()) { + processing_lock_.unlock(); + return false; + } else { + return true; + } +} + void Node::CopyInputs(Node *source, Node *destination) { Q_ASSERT(source->id() == destination->id()); @@ -290,6 +300,17 @@ QString Node::Code(NodeOutput *output) return QString(); } +NodeParam *Node::GetParameterWithID(const QString &id) +{ + foreach (NodeParam* param, params_) { + if (param->id() == id) { + return param; + } + } + + return nullptr; +} + bool Node::OutputsTo(Node *n) { foreach (NodeParam* param, params_) { diff --git a/app/node/node.h b/app/node/node.h index 6a31a6b14..d3bba3de7 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -129,6 +129,11 @@ public: */ virtual QString Code(NodeOutput* output); + /** + * @brief Returns the parameter with the specified ID (or nullptr if it doesn't exist) + */ + NodeParam* GetParameterWithID(const QString& id); + /** * @brief Returns whether this Node outputs data to the Node `n` in any way */ @@ -199,6 +204,7 @@ public: */ void LockProcessing(); void UnlockProcessing(); + bool IsProcessingLocked(); /** * @brief Copies inputs from from Node to another including connections diff --git a/app/render/backend/CMakeLists.txt b/app/render/backend/CMakeLists.txt index 03456983d..9cc8c3cea 100644 --- a/app/render/backend/CMakeLists.txt +++ b/app/render/backend/CMakeLists.txt @@ -19,11 +19,14 @@ add_subdirectory(vulkan) set(OLIVE_SOURCES ${OLIVE_SOURCES} + render/backend/renderbackend.h render/backend/renderbackend.cpp + render/backend/audiorenderbackend.h render/backend/audiorenderbackend.cpp render/backend/videorenderbackend.h render/backend/videorenderbackend.cpp + PARENT_SCOPE ) diff --git a/app/render/backend/opengl/CMakeLists.txt b/app/render/backend/opengl/CMakeLists.txt index 9b7f998b1..ca7564f30 100644 --- a/app/render/backend/opengl/CMakeLists.txt +++ b/app/render/backend/opengl/CMakeLists.txt @@ -16,6 +16,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + render/backend/opengl/decodercache.h + render/backend/opengl/decodercache.cpp render/backend/opengl/functions.h render/backend/opengl/functions.cpp render/backend/opengl/openglbackend.h @@ -24,6 +26,8 @@ set(OLIVE_SOURCES render/backend/opengl/openglframebuffer.cpp render/backend/opengl/openglshader.h render/backend/opengl/openglshader.cpp + render/backend/opengl/openglshadercache.h + render/backend/opengl/openglshadercache.cpp render/backend/opengl/opengltexture.h render/backend/opengl/opengltexture.cpp render/backend/opengl/openglworker.h diff --git a/app/render/backend/opengl/decodercache.cpp b/app/render/backend/opengl/decodercache.cpp index 09a2df6e4..29939c289 100644 --- a/app/render/backend/opengl/decodercache.cpp +++ b/app/render/backend/opengl/decodercache.cpp @@ -4,3 +4,18 @@ DecoderCache::DecoderCache() { } + +void DecoderCache::Clear() +{ + decoders_.clear(); +} + +void DecoderCache::AddDecoder(Stream *stream, DecoderPtr shader) +{ + decoders_.insert(stream, shader); +} + +DecoderPtr DecoderCache::GetDecoder(Stream *stream) +{ + return decoders_.value(stream); +} diff --git a/app/render/backend/opengl/decodercache.h b/app/render/backend/opengl/decodercache.h index 48441a3fd..47011e965 100644 --- a/app/render/backend/opengl/decodercache.h +++ b/app/render/backend/opengl/decodercache.h @@ -1,11 +1,26 @@ #ifndef DECODERCACHE_H #define DECODERCACHE_H +#include "decoder/decoder.h" +#include "project/item/footage/stream.h" +/** + * @brief Thread-safe cache of decoders + */ class DecoderCache { public: DecoderCache(); + + void Clear(); + + void AddDecoder(Stream* stream, DecoderPtr shader); + + DecoderPtr GetDecoder(Stream* stream); + +private: + QMap decoders_; + }; #endif // DECODERCACHE_H diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index f26a9dbc6..9744788e9 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -34,7 +34,7 @@ bool OpenGLBackend::Init() QThread* thread = threads().at(i); // Create one processor object for each thread - OpenGLWorker* processor = new OpenGLWorker(share_ctx); + OpenGLWorker* processor = new OpenGLWorker(share_ctx, &shader_cache_, &decoder_cache_); processor->SetParameters(params()); // Finally, we can move it to its own thread @@ -130,12 +130,12 @@ bool OpenGLBackend::Compile() void OpenGLBackend::Decompile() { - compiled_nodes_.clear(); + shader_cache_.Clear(); } -bool OpenGLBackend::TraverseCompiling(Node *n) +bool OpenGLBackend::TraverseCompiling(Node *) { - foreach (NodeParam* param, n->parameters()) { + /*foreach (NodeParam* param, n->parameters()) { if (param->type() == NodeParam::kInput && param->IsConnected()) { NodeOutput* connected_output = static_cast(param)->get_connected_output(); @@ -183,28 +183,11 @@ bool OpenGLBackend::TraverseCompiling(Node *n) return false; } } - } + }*/ return true; } -OpenGLShaderPtr OpenGLBackend::GetShaderFromID(const QString &id) -{ - foreach (const CompiledNode& info, compiled_nodes_) { - if (info.id == id) { - return info.program; - } - } - - return nullptr; -} - -QString OpenGLBackend::GenerateShaderID(NodeOutput *output) -{ - // Creates a unique identifier for this specific node and this specific output - return QString("%1:%2").arg(output->parent()->id(), output->id()); -} - void OpenGLBackend::ThreadCallback(OpenGLTexturePtr texture, const rational& time, const QByteArray& hash) { // Threads are all done now, time to proceed diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/backend/opengl/openglbackend.h index 432ed88e2..6a52a4b3c 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/backend/opengl/openglbackend.h @@ -2,10 +2,12 @@ #define OPENGLBACKEND_H #include "../videorenderbackend.h" +#include "decodercache.h" #include "openglframebuffer.h" #include "openglworker.h" #include "opengltexture.h" #include "openglshader.h" +#include "openglshadercache.h" class OpenGLBackend : public VideoRenderBackend { @@ -30,19 +32,8 @@ protected: virtual void GenerateFrame(const rational& time) override; private: - struct CompiledNode { - QString id; - OpenGLShaderPtr program; - }; - bool TraverseCompiling(Node* n); - OpenGLShaderPtr GetShaderFromID(const QString& id); - - QString GenerateShaderID(NodeOutput* output); - - QList compiled_nodes_; - QVector processors_; OpenGLTexturePtr master_texture_; @@ -51,6 +42,9 @@ private: OpenGLFramebuffer copy_buffer_; OpenGLShaderPtr copy_pipeline_; + OpenGLShaderCache shader_cache_; + DecoderCache decoder_cache_; + private slots: void ThreadCallback(OpenGLTexturePtr texture, const rational& time, const QByteArray& hash); diff --git a/app/render/backend/opengl/openglshadercache.cpp b/app/render/backend/opengl/openglshadercache.cpp index f58d4e9ea..724c1cadc 100644 --- a/app/render/backend/opengl/openglshadercache.cpp +++ b/app/render/backend/opengl/openglshadercache.cpp @@ -1,6 +1,29 @@ #include "openglshadercache.h" +#include "node/node.h" + OpenGLShaderCache::OpenGLShaderCache() { } + +QString OpenGLShaderCache::GenerateShaderID(NodeOutput *output) +{ + // Creates a unique identifier for this specific node and this specific output + return QString("%1:%2").arg(output->parent()->id(), output->id()); +} + +void OpenGLShaderCache::Clear() +{ + compiled_nodes_.clear(); +} + +void OpenGLShaderCache::AddShader(NodeOutput *output, OpenGLShaderPtr shader) +{ + compiled_nodes_.insert(GenerateShaderID(output), shader); +} + +OpenGLShaderPtr OpenGLShaderCache::GetShader(NodeOutput *output) +{ + return compiled_nodes_.value(GenerateShaderID(output)); +} diff --git a/app/render/backend/opengl/openglshadercache.h b/app/render/backend/opengl/openglshadercache.h index 9b736c210..16408db27 100644 --- a/app/render/backend/opengl/openglshadercache.h +++ b/app/render/backend/opengl/openglshadercache.h @@ -1,11 +1,30 @@ #ifndef OPENGLSHADERCACHE_H #define OPENGLSHADERCACHE_H +#include +#include "node/output.h" +#include "openglshader.h" + +/** + * @brief Thread-safe cache of OpenGL shaders + */ class OpenGLShaderCache { public: OpenGLShaderCache(); + + void Clear(); + + void AddShader(NodeOutput* output, OpenGLShaderPtr shader); + + OpenGLShaderPtr GetShader(NodeOutput* output); + +private: + QString GenerateShaderID(NodeOutput* output); + + QMap compiled_nodes_; + }; #endif // OPENGLSHADERCACHE_H diff --git a/app/render/backend/opengl/opengltexture.cpp b/app/render/backend/opengl/opengltexture.cpp index 413fcb5f8..bc67bb42c 100644 --- a/app/render/backend/opengl/opengltexture.cpp +++ b/app/render/backend/opengl/opengltexture.cpp @@ -75,6 +75,16 @@ void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const oli } } +void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame, const Type &type) +{ + Create(ctx, frame->width(), frame->height(), frame->format(), type, frame->data()); +} + +void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame) +{ + Create(ctx, frame, kSingleBuffer); +} + void OpenGLTexture::Destroy() { if (context_ != nullptr) { diff --git a/app/render/backend/opengl/opengltexture.h b/app/render/backend/opengl/opengltexture.h index 952dae5a9..3c7cfca9f 100644 --- a/app/render/backend/opengl/opengltexture.h +++ b/app/render/backend/opengl/opengltexture.h @@ -24,6 +24,7 @@ #include #include +#include "decoder/frame.h" #include "render/pixelformat.h" /** @@ -45,6 +46,8 @@ public: void Create(QOpenGLContext* ctx, int width, int height, const olive::PixelFormat &format, void *data = nullptr); void Create(QOpenGLContext* ctx, int width, int height, const olive::PixelFormat &format, const Type& type, void *data = nullptr); + void Create(QOpenGLContext* ctx, FramePtr frame, const Type& type); + void Create(QOpenGLContext* ctx, FramePtr frame); bool IsCreated() const; diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index 5d5c73386..9664961a5 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -1,12 +1,17 @@ #include "openglworker.h" +#include + +#include "node/block/block.h" #include "node/node.h" -OpenGLWorker::OpenGLWorker(QOpenGLContext *share_ctx, QObject *parent) : +OpenGLWorker::OpenGLWorker(QOpenGLContext *share_ctx, OpenGLShaderCache *shader_cache, DecoderCache *decoder_cache, QObject *parent) : QObject(parent), share_ctx_(share_ctx), ctx_(nullptr), - functions_(nullptr) + functions_(nullptr), + shader_cache_(shader_cache), + decoder_cache_(decoder_cache) { surface_.create(); } @@ -93,6 +98,169 @@ void OpenGLWorker::UpdateViewportFromParams() } } +Node *OpenGLWorker::ValidateBlock(Node *n, const rational& time) +{ + if (n->IsBlock()) { + Block* block = static_cast(n); + + while (block->in() > time && block != nullptr) { + // This Block is too late, find an earlier one + block = block->previous(); + } + + while (block->out() <= time && block != nullptr) { + // 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; + } + + return n; +} + +QList OpenGLWorker::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); + + // Access a map of Node inputs and decoder instances and retrieve a frame! + StreamPtr stream = input->get_value_at_time(0).value(); + DecoderPtr decoder = decoder_cache_->GetDecoder(stream.get()); + + if (decoder == nullptr && stream != nullptr) { + // Init decoder + decoder = Decoder::CreateFromID(stream->footage()->decoder()); + decoder->set_stream(stream); + decoder_cache_->AddDecoder(stream.get(), decoder); + } + + // 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) { + OpenGLTexturePtr footage_tex = std::make_shared(); + footage_tex->Create(ctx_, frame, OpenGLTexture::kDoubleBuffer); + + // FIXME: Alpha association and color management + + input->set_stored_value(QVariant::fromValue(footage_tex)); + } + } + } + } + } + } + + return connected_inputs; +} + +void OpenGLWorker::RunNodeAsShader(Node* node, OpenGLShaderPtr shader) +{ + shader->bind(); + + unsigned int input_texture_count = 0; + + foreach (NodeParam* param, node->parameters()) { + if (param->type() == NodeParam::kInput) { + // See if the shader has takes this parameter as an input + int variable_location = shader->uniformLocation(param->id()); + + if (variable_location > -1) { + // This variable is used in the shader, let's set it to our value + + NodeInput* input = static_cast(param); + switch (input->data_type()) { + case NodeInput::kInt: + shader->setUniformValue(variable_location, input->value().toInt()); + break; + case NodeInput::kFloat: + shader->setUniformValue(variable_location, input->value().toFloat()); + break; + case NodeInput::kVec2: + shader->setUniformValue(variable_location, input->value().value()); + break; + case NodeInput::kVec3: + shader->setUniformValue(variable_location, input->value().value()); + break; + case NodeInput::kVec4: + shader->setUniformValue(variable_location, input->value().value()); + break; + case NodeInput::kMatrix: + shader->setUniformValue(variable_location, input->value().value()); + break; + case NodeInput::kColor: + shader->setUniformValue(variable_location, input->value().value()); + break; + case NodeInput::kBoolean: + shader->setUniformValue(variable_location, input->value().toBool()); + break; + case NodeInput::kTexture: + case NodeInput::kFootage: + { + OpenGLTexturePtr texture = input->value().value(); + 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); + + input_texture_count++; + break; + } + case NodeInput::kAny: + case NodeInput::kSamples: + case NodeInput::kTrack: + case NodeInput::kString: + case NodeInput::kRational: + case NodeInput::kBlock: + case NodeInput::kFont: + case NodeInput::kFile: + case NodeInput::kNone: + break; + } + } + } + } + + // Attach texture to framebuffer + // Bind framebuffer + // Release framebuffer + // Detach texture + + // Release any textures we bound before + while (input_texture_count > 0) { + input_texture_count--; + + // Release texture here + functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count); + functions_->glBindTexture(GL_TEXTURE_2D, 0); + } + + shader->release(); +} + void OpenGLWorker::FinishInit() { // Make context current on that surface @@ -117,20 +285,103 @@ void OpenGLWorker::RenderAsSibling(const NodeDependency &dep) { NodeOutput* output = dep.node(); Node* node = output->parent(); + rational time = dep.in(); node->LockProcessing(); - // Check if block - // If yes, traverse previous and next until we find the right block - // Check all inputs for ones that are dependents - // If input is NOT connected, set stored value to keyframe at time - // Some inputs we handle ourselves such as FOOTAGE - // If input IS connected, we need to traverse down it - // If more than one input is connected, signal out for a sibling to handle it - // Keep iterating inputs until all are up to date - // Finally get value from output - // If we have shader code, the output is a texture and we handle the I/O - // If we don't, the output is some other value and the Node will produce the output + // 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(node, time)) == nullptr) { + // ValidateBlock() may have returned nullptr if there was no Block found at this time so no texture to return + dep.node()->cache_value(dep.range(), 0); + node->UnlockProcessing(); + return; + } + + // Ensure output is the output matching the node as it may have changed + output = static_cast(node->GetParameterWithID(output->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 + dep.node()->cache_value(dep.range(), output->get_cached_value(dep.range())); + node->UnlockProcessing(); + return; + } + + // 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 + OpenGLShaderPtr shader = shader_cache_->GetShader(output); + + if (shader != nullptr) { + // Run code + RunNodeAsShader(node, shader); + } else { + // Generate the value as expected + QVariant value = node->Value(output); + + // Place the value into the output + output->cache_value(dep.range(), value); + } + + // We're done! node->UnlockProcessing(); } diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h index cf9101f19..314f47d63 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/backend/opengl/openglworker.h @@ -5,14 +5,16 @@ #include #include +#include "decodercache.h" #include "node/dependency.h" #include "openglframebuffer.h" +#include "openglshadercache.h" #include "render/videoparams.h" class OpenGLWorker : public QObject { Q_OBJECT public: - OpenGLWorker(QOpenGLContext* share_ctx, QObject* parent = nullptr); + OpenGLWorker(QOpenGLContext* share_ctx, OpenGLShaderCache* shader_cache, DecoderCache* decoder_cache, QObject* parent = nullptr); virtual ~OpenGLWorker() override; @@ -51,12 +53,19 @@ public slots: void Render(const NodeDependency& path); signals: + void RequestSibling(const NodeDependency& path); private: void ProcessNode(); void UpdateViewportFromParams(); + Node* ValidateBlock(Node* n, const rational& time); + + QList ProcessNodeInputsForTime(Node* n, const TimeRange& time); + + void RunNodeAsShader(Node *node, OpenGLShaderPtr shader); + QOpenGLContext* share_ctx_; QOpenGLContext* ctx_; @@ -68,6 +77,10 @@ private: VideoRenderingParams video_params_; + OpenGLShaderCache* shader_cache_; + + DecoderCache* decoder_cache_; + private slots: void FinishInit(); diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 78f7455f3..fa94ddd14 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -187,6 +187,7 @@ void VideoRenderBackend::Close() foreach (QThread* thread, threads_) { thread->quit(); + thread->wait(); // FIXME: Maximum time in case a thread is stuck? } threads_.clear();