From f424d8b44eb4bcb8cf1a202ad2d265c04576d0f7 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 5 Dec 2019 03:53:52 +1100 Subject: [PATCH] rewrote renderers where necessary for new node structure Largely conforming renderers to new NodeValue system. Code seems a lot cleaner this way which is a nice advantage. Likely non-functional as this won't compile just yet and still needs probably another day or two of testing to get it back to where it was before. --- app/node/value.cpp | 55 ++++---- app/node/value.h | 2 + app/render/backend/audio/audiobackend.cpp | 6 +- app/render/backend/audio/audiobackend.h | 2 +- app/render/backend/audio/audioworker.cpp | 4 +- app/render/backend/audio/audioworker.h | 2 +- app/render/backend/audiorenderworker.cpp | 101 ++++---------- app/render/backend/audiorenderworker.h | 7 +- app/render/backend/opengl/openglworker.cpp | 2 +- app/render/backend/opengl/openglworker.h | 2 +- app/render/backend/renderworker.cpp | 153 +++++++-------------- app/render/backend/renderworker.h | 14 +- app/render/backend/videorenderworker.cpp | 59 +++----- app/render/backend/videorenderworker.h | 8 +- 14 files changed, 154 insertions(+), 263 deletions(-) diff --git a/app/node/value.cpp b/app/node/value.cpp index 8c066cf65..1388f860b 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -27,31 +27,7 @@ void NodeValueDatabase::Insert(const NodeInput *key, const NodeValueTable &value NodeValueTable NodeValueDatabase::Merge() const { - if (tables_.size() == 1) { - return tables_.begin().value(); - } - - int row = 0; - - NodeValueTable merged_table; - - QHash::const_iterator iterator; - - // Slipstreams all tables together - // FIXME: I don't actually know if this is the right approach... - for (iterator = tables_.begin();iterator != tables_.end();iterator++) { - const NodeValueTable& table = iterator.value(); - - if (row >= table.Count()) { - continue; - } - - int row_index = table.Count() - 1 - row; - - merged_table.Prepend(table.At(row_index)); - } - - return merged_table; + return NodeValueTable::Merge(tables_.values()); } NodeValue::NodeValue(const NodeParam::DataType &type, const QVariant &data, const QString &tag) : @@ -125,6 +101,35 @@ bool NodeValueTable::isEmpty() const return values_.isEmpty(); } +NodeValueTable NodeValueTable::Merge(QList tables) +{ + + + if (tables.size() == 1) { + return tables.first(); + } + + int row = 0; + + NodeValueTable merged_table; + + QHash::const_iterator iterator; + + // Slipstreams all tables together + // FIXME: I don't actually know if this is the right approach... + foreach (const NodeValueTable& t, tables) { + if (row >= t.Count()) { + continue; + } + + int row_index = t.Count() - 1 - row; + + merged_table.Prepend(t.At(row_index)); + } + + return merged_table; +} + QVariant NodeValueTable::GetInternal(const NodeParam::DataType &type, const QString &tag, bool remove) { int index = -1; diff --git a/app/node/value.h b/app/node/value.h index f9cafa76a..a4d6d876e 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -37,6 +37,8 @@ public: bool isEmpty() const; + static NodeValueTable Merge(QList tables); + private: QVariant GetInternal(const NodeParam::DataType& type, const QString& tag, bool remove); diff --git a/app/render/backend/audio/audiobackend.cpp b/app/render/backend/audio/audiobackend.cpp index e50328d4a..a05e172b8 100644 --- a/app/render/backend/audio/audiobackend.cpp +++ b/app/render/backend/audio/audiobackend.cpp @@ -62,14 +62,14 @@ void AudioBackend::DecompileInternal() void AudioBackend::ConnectWorkerToThis(RenderWorker *worker) { - connect(worker, SIGNAL(CompletedCache(NodeDependency)), this, SLOT(ThreadCompletedCache(NodeDependency))); + connect(worker, SIGNAL(CompletedCache(NodeDependency, QVariant)), this, SLOT(ThreadCompletedCache(NodeDependency, QVariant))); } -void AudioBackend::ThreadCompletedCache(NodeDependency dep) +void AudioBackend::ThreadCompletedCache(NodeDependency dep, QVariant data) { caching_ = false; - QByteArray cached_samples = dep.node()->get_cached_value(dep.range()).toByteArray(); + QByteArray cached_samples = data.toByteArray(); int offset = params().time_to_bytes(dep.in()); int length = params().time_to_bytes(dep.range().length()); diff --git a/app/render/backend/audio/audiobackend.h b/app/render/backend/audio/audiobackend.h index 3fca71a12..9d060e52c 100644 --- a/app/render/backend/audio/audiobackend.h +++ b/app/render/backend/audio/audiobackend.h @@ -30,7 +30,7 @@ protected: virtual void ConnectWorkerToThis(RenderWorker* worker) override; private slots: - void ThreadCompletedCache(NodeDependency dep); + void ThreadCompletedCache(NodeDependency dep, QVariant data); private: QFile pull_device_; diff --git a/app/render/backend/audio/audioworker.cpp b/app/render/backend/audio/audioworker.cpp index b7068bc4c..333f8c03c 100644 --- a/app/render/backend/audio/audioworker.cpp +++ b/app/render/backend/audio/audioworker.cpp @@ -16,8 +16,8 @@ bool AudioWorker::OutputIsAccelerated(NodeOutput *output) return false; } -QVariant AudioWorker::RunNodeAccelerated(NodeOutput *output) +NodeValueTable AudioWorker::RunNodeAccelerated(NodeOutput *output) { Q_UNUSED(output) - return QVariant(); + return NodeValueTable(); } diff --git a/app/render/backend/audio/audioworker.h b/app/render/backend/audio/audioworker.h index b8c42f956..af3e9747b 100644 --- a/app/render/backend/audio/audioworker.h +++ b/app/render/backend/audio/audioworker.h @@ -13,7 +13,7 @@ protected: virtual bool OutputIsAccelerated(NodeOutput *output) override; - virtual QVariant RunNodeAccelerated(NodeOutput *output) override; + virtual NodeValueTable RunNodeAccelerated(NodeOutput *output) override; private: diff --git a/app/render/backend/audiorenderworker.cpp b/app/render/backend/audiorenderworker.cpp index 24908cd44..0927cf153 100644 --- a/app/render/backend/audiorenderworker.cpp +++ b/app/render/backend/audiorenderworker.cpp @@ -12,35 +12,6 @@ void AudioRenderWorker::SetParameters(const AudioRenderingParams &audio_params) audio_params_ = audio_params; } -QVariant AudioRenderWorker::RenderAsSibling(NodeDependency dep) -{ - NodeOutput* output = dep.node(); - Node* node = output->parentNode(); - QList connected_inputs; - QVariant value; - - // Set working state - working_++; - - // 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 { - value = ProcessNodeNormally(NodeDependency(output, dep.range())); - } - - // We're done! - - // End this working state - working_--; - - return value; -} - bool AudioRenderWorker::InitInternal() { // Nothing to init yet @@ -52,63 +23,51 @@ void AudioRenderWorker::CloseInternal() // Nothing to init yet } -QVariant AudioRenderWorker::RenderBlock(NodeOutput* output, const TimeRange &range) +FramePtr AudioRenderWorker::RetrieveFromDecoder(DecoderPtr decoder, const TimeRange &range) +{ + return decoder->RetrieveAudio(range.in(), range.out() - range.in(), audio_params_); +} + +NodeValueTable AudioRenderWorker::RenderBlock(NodeOutput* output, const TimeRange &range) { QList active_blocks = ValidateBlockRange(static_cast(output->parentNode()), 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); + NodeValueTable merged_table; + // Loop through active blocks retrieving their audio - while (!active_blocks.isEmpty()) { - int block_for_this_thread = -1; + foreach (Block* b, active_blocks) { + TimeRange range_for_block(qMax(b->in(), range.in()), + qMin(b->out(), range.out())); - for (int i=0;i(b->GetParameterWithID(output->id())); + NodeValueTable table = RenderAsSibling(NodeDependency(b->block_output(), + range_for_block)); - TimeRange range_for_block(qMax(b->in(), range.in()), - qMin(b->out(), range.out())); + QByteArray samples_from_this_block = table.Take(NodeParam::kSamples).toByteArray(); + int destination_offset = audio_params_.time_to_bytes(range_for_block.in() - range.in()); + int maximum_copy_size = audio_params_.time_to_bytes(range_for_block.length()); + int copied_size = 0; - // 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(); + if (!samples_from_this_block.isEmpty()) { + copied_size = samples_from_this_block.size(); - 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)); - } - } + memcpy(block_range_buffer.data()+destination_offset, + samples_from_this_block.data(), + static_cast(copied_size)); } - 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); + if (copied_size < maximum_copy_size) { + memset(block_range_buffer.data()+destination_offset+copied_size, + 0, + static_cast(maximum_copy_size - copied_size)); } + + NodeValueTable::Merge({merged_table, table}); } - return block_range_buffer; -} + merged_table.Push(NodeParam::kSamples, block_range_buffer); -FramePtr AudioRenderWorker::RetrieveFromDecoder(DecoderPtr decoder, const TimeRange &range) -{ - return decoder->RetrieveAudio(range.in(), range.out() - range.in(), audio_params_); + return merged_table; } diff --git a/app/render/backend/audiorenderworker.h b/app/render/backend/audiorenderworker.h index f03419c5a..cc7df4494 100644 --- a/app/render/backend/audiorenderworker.h +++ b/app/render/backend/audiorenderworker.h @@ -11,18 +11,15 @@ public: void SetParameters(const AudioRenderingParams& audio_params); -public slots: - virtual QVariant RenderAsSibling(NodeDependency dep) override; - protected: virtual bool InitInternal() override; virtual void CloseInternal() override; - QVariant RenderBlock(NodeOutput *output, const TimeRange& range); - virtual FramePtr RetrieveFromDecoder(DecoderPtr decoder, const TimeRange& range) override; + virtual NodeValueTable RenderBlock(NodeOutput *output, const TimeRange& range) override; + private: AudioRenderingParams audio_params_; diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index fe2d58cb2..f2af33cac 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -83,7 +83,7 @@ void OpenGLWorker::ParametersChangedEvent() } } -QVariant OpenGLWorker::RunNodeAccelerated(NodeOutput *out) +NodeValueTable OpenGLWorker::RunNodeAccelerated(NodeOutput *out) { OpenGLShaderPtr shader = shader_cache_->GetShader(out); Node* node = out->parentNode(); diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h index 556efb6da..4ee18f766 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/backend/opengl/openglworker.h @@ -49,7 +49,7 @@ protected: virtual bool OutputIsAccelerated(NodeOutput *output) override; - virtual QVariant RunNodeAccelerated(NodeOutput *output) override; + virtual NodeValueTable RunNodeAccelerated(NodeOutput *output) override; virtual void TextureToBuffer(const QVariant& texture, QByteArray& buffer) override; diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index e88266d3f..2187ed71e 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -10,7 +10,6 @@ RenderWorker::RenderWorker(DecoderCache *decoder_cache, QObject *parent) : started_(false), decoder_cache_(decoder_cache) { - } bool RenderWorker::IsAvailable() @@ -40,9 +39,36 @@ void RenderWorker::Close() void RenderWorker::Render(NodeDependency path) { - RenderInternal(path); + emit CompletedCache(path, RenderInternal(path)); +} - emit CompletedCache(path); +NodeValueTable RenderWorker::RenderAsSibling(NodeDependency dep) +{ + NodeOutput* output = dep.node(); + Node* node = output->parentNode(); + QList connected_inputs; + NodeValueTable value; + + // Set working state + working_++; + + // 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 { + value = ProcessNodeNormally(NodeDependency(output, dep.range())); + } + + // We're done! + + // End this working state + working_--; + + return value; } DecoderCache *RenderWorker::decoder_cache() @@ -93,9 +119,9 @@ QList RenderWorker::ValidateBlockRange(Block *n, const TimeRange &range return list; } -void RenderWorker::RenderInternal(const NodeDependency &path) +NodeValueTable RenderWorker::RenderInternal(const NodeDependency &path) { - RenderAsSibling(path); + return RenderAsSibling(path); } StreamPtr RenderWorker::ResolveStreamFromInput(NodeInput *input) @@ -124,116 +150,35 @@ 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 = RetrieveFromDecoder(decoder, time); - - if (frame != nullptr) { - QVariant value = FrameToValue(frame); - - input->set_stored_value(value); - } - } - } - } - } - } - - return connected_inputs; -} - -QVariant RenderWorker::ProcessNodeNormally(const NodeDependency& dep) +NodeValueTable RenderWorker::ProcessNodeNormally(const NodeDependency& dep) { NodeOutput* output = dep.node(); Node* node = dep.node()->parentNode(); //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()); - } + // FIXME: Cache certain values here if we've already processed them before - // We need to run the Node's code to get the correct value for this time + NodeValueDatabase database; - 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(); + // We need to insert tables into the database for each input + foreach (NodeParam* param, node->parameters()) { + if (param->type() == NodeParam::kInput) { + NodeValueTable table; + NodeInput* input = static_cast(param); 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--; + if (input->IsConnected()) { + // Value will equal something from the connected node, follow it + table = ProcessNodeNormally(NodeDependency(input->get_connected_output(), + input_time)); + } else { + // Push onto the table the value at this time from the input + QVariant input_value = input->get_value_at_time(input_time.in()); + table.Push(input->data_type(), input_value); } - } - // 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); + database.Insert(input, table); } } @@ -245,6 +190,6 @@ QVariant RenderWorker::ProcessNodeNormally(const NodeDependency& dep) return RunNodeAccelerated(output); } else { // Generate the value as expected - return node->Value(output); + return node->Value(database); } } diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index 07e0ac6dc..83ff2bb84 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -27,12 +27,12 @@ public slots: void Render(NodeDependency path); - virtual QVariant RenderAsSibling(NodeDependency dep) = 0; + NodeValueTable RenderAsSibling(NodeDependency dep); signals: void RequestSibling(NodeDependency path); - void CompletedCache(NodeDependency dep); + void CompletedCache(NodeDependency dep, NodeValueTable data); protected: /** @@ -64,22 +64,22 @@ protected: virtual void CloseInternal() = 0; - virtual void RenderInternal(const NodeDependency& path); + virtual NodeValueTable RenderInternal(const NodeDependency& path); virtual bool OutputIsAccelerated(NodeOutput *output) = 0; - virtual QVariant RunNodeAccelerated(NodeOutput *output) = 0; + virtual NodeValueTable RunNodeAccelerated(NodeOutput *output) = 0; StreamPtr ResolveStreamFromInput(NodeInput* input); DecoderPtr ResolveDecoderFromInput(NodeInput* input); virtual FramePtr RetrieveFromDecoder(DecoderPtr decoder, const TimeRange& range) = 0; - QList ProcessNodeInputsForTime(Node* n, const TimeRange& time); - virtual QVariant FrameToValue(FramePtr frame) = 0; - QVariant ProcessNodeNormally(const NodeDependency &dep); + NodeValueTable ProcessNodeNormally(const NodeDependency &dep); + + virtual NodeValueTable RenderBlock(NodeOutput *output, const TimeRange& range) = 0; DecoderCache* decoder_cache(); diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index d8e84ffcf..67dfffb0b 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -16,7 +16,7 @@ const VideoRenderingParams &VideoRenderWorker::video_params() return video_params_; } -void VideoRenderWorker::RenderInternal(const NodeDependency& path) +NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path) { // Get hash of node graph // We use SHA-1 for speed (benchmarks show it's the fastest hash available to us) @@ -24,18 +24,22 @@ void VideoRenderWorker::RenderInternal(const NodeDependency& path) HashNodeRecursively(&hasher, path.node()->parentNode(), path.in()); QByteArray hash = hasher.result(); + NodeValueTable value; + if (frame_cache_->HasHash(hash)) { // We've already cached this hash, no need to continue emit HashAlreadyExists(path, hash); } else if (frame_cache_->TryCache(hash)) { // This hash is available for us to cache, start traversing graph - QVariant value = RenderAsSibling(path); + value = RenderAsSibling(path); emit CompletedFrame(path, hash, value); } else { // Another thread must be caching this already, nothing to be done emit HashAlreadyBeingCached(); } + + return value; } FramePtr VideoRenderWorker::RetrieveFromDecoder(DecoderPtr decoder, const TimeRange &range) @@ -120,42 +124,6 @@ void VideoRenderWorker::CloseInternal() download_buffer_.clear(); } -QVariant VideoRenderWorker::RenderAsSibling(NodeDependency dep) -{ - NodeOutput* output = dep.node(); - Node* original_node = output->parentNode(); - Node* node; - rational time = dep.in(); - QVariant value; - - // Set working state - working_++; - - //qDebug() << "Processing" << original_node->id() << original_node; - - // 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 (original_node->IsBlock()) { - node = ValidateBlock(static_cast(original_node), time); - - if (original_node != node) { - // Ensure output is the output matching the node as it may have changed - output = static_cast(node->GetParameterWithID(output->id())); - } - } else { - node = original_node; - } - - value = ProcessNodeNormally(NodeDependency(output, dep.range())); - - // We're done! - - // End this working state - working_--; - - return value; -} - void VideoRenderWorker::Download(NodeDependency dep, QByteArray hash, QVariant texture, QString filename) { working_++; @@ -184,3 +152,18 @@ void VideoRenderWorker::Download(NodeDependency dep, QByteArray hash, QVariant t working_--; } + +NodeValueTable VideoRenderWorker::RenderBlock(NodeOutput* output, const TimeRange &range) +{ + // A frame can only have one active block so we just validate the in point of the range + Block* active_block = ValidateBlock(static_cast(output->parentNode()), range.in()); + + NodeValueTable table; + + if (active_block) { + table = RenderAsSibling(NodeDependency(active_block->block_output(), + range)); + } + + return table; +} diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h index 15252dac8..7efe595fb 100644 --- a/app/render/backend/videorenderworker.h +++ b/app/render/backend/videorenderworker.h @@ -16,12 +16,10 @@ public: void SetParameters(const VideoRenderingParams& video_params); public slots: - virtual QVariant RenderAsSibling(NodeDependency dep) override; - void Download(NodeDependency dep, QByteArray hash, QVariant texture, QString filename); signals: - void CompletedFrame(NodeDependency path, QByteArray hash, QVariant value); + void CompletedFrame(NodeDependency path, QByteArray hash, NodeValueTable value); void CompletedDownload(NodeDependency path, QByteArray hash); @@ -40,10 +38,12 @@ protected: virtual void TextureToBuffer(const QVariant& texture, QByteArray& buffer) = 0; - virtual void RenderInternal(const NodeDependency& path) override; + virtual NodeValueTable RenderInternal(const NodeDependency& path) override; virtual FramePtr RetrieveFromDecoder(DecoderPtr decoder, const TimeRange& range) override; + virtual NodeValueTable RenderBlock(NodeOutput *output, const TimeRange& range) override; + private: void ProcessNode();