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.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<Stream*, DecoderPtr> decoders_;
|
||||
|
||||
};
|
||||
|
||||
#endif // DECODERCACHE_H
|
||||
|
||||
@@ -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<NodeInput*>(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
|
||||
|
||||
@@ -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<CompiledNode> compiled_nodes_;
|
||||
|
||||
QVector<OpenGLWorker*> 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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
#ifndef OPENGLSHADERCACHE_H
|
||||
#define OPENGLSHADERCACHE_H
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#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<QString, OpenGLShaderPtr> compiled_nodes_;
|
||||
|
||||
};
|
||||
|
||||
#endif // OPENGLSHADERCACHE_H
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <memory>
|
||||
#include <QOpenGLFunctions>
|
||||
|
||||
#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;
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
#include "openglworker.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#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<Block*>(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<NodeInput*> OpenGLWorker::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);
|
||||
|
||||
// Access a map of Node inputs and decoder instances and retrieve a frame!
|
||||
StreamPtr stream = input->get_value_at_time(0).value<StreamPtr>();
|
||||
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<OpenGLTexture>();
|
||||
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<NodeInput*>(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<QVector2D>());
|
||||
break;
|
||||
case NodeInput::kVec3:
|
||||
shader->setUniformValue(variable_location, input->value().value<QVector3D>());
|
||||
break;
|
||||
case NodeInput::kVec4:
|
||||
shader->setUniformValue(variable_location, input->value().value<QVector4D>());
|
||||
break;
|
||||
case NodeInput::kMatrix:
|
||||
shader->setUniformValue(variable_location, input->value().value<QMatrix4x4>());
|
||||
break;
|
||||
case NodeInput::kColor:
|
||||
shader->setUniformValue(variable_location, input->value().value<QColor>());
|
||||
break;
|
||||
case NodeInput::kBoolean:
|
||||
shader->setUniformValue(variable_location, input->value().toBool());
|
||||
break;
|
||||
case NodeInput::kTexture:
|
||||
case NodeInput::kFootage:
|
||||
{
|
||||
OpenGLTexturePtr texture = input->value().value<OpenGLTexturePtr>();
|
||||
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<NodeOutput*>(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<NodeInput*> 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--;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -5,14 +5,16 @@
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
|
||||
#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<NodeInput*> 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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user