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.
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
#include "videorenderworker.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#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<Block*>(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<NodeInput*>(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<NodeInput*> VideoRenderWorker::ProcessNodeInputsForTime(Node *n, const TimeRange &time)
|
||||
{
|
||||
QList<NodeInput*> 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<NodeInput*>(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<NodeInput*> 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<Block*>(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<NodeOutput*>(node->GetParameterWithID(output->id()));
|
||||
|
||||
if (original_node != node) {
|
||||
// Ensure output is the output matching the node as it may have changed
|
||||
output = static_cast<NodeOutput*>(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;i<connected_inputs.size();i++) {
|
||||
NodeInput* input = connected_inputs.at(i);
|
||||
NodeOutput* connected_output = input->get_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;i<connected_inputs.size();i++) {
|
||||
NodeInput* input = connected_inputs.at(i);
|
||||
|
||||
// If this node is locked, we assume it's already being processed. Otherwise we need to request a sibling
|
||||
if (!input->get_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<OIIO::ImageOutput> 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();
|
||||
|
||||
Reference in New Issue
Block a user