nodes: minor overhaul to functionality
The nodes now have more control over how their accelerated shaders/sample functions are run, as well as how items are popped off the value tables. This allows for various optimizations that we didn't have access to before.
This commit is contained in:
@@ -40,6 +40,7 @@ set(OLIVE_SOURCES
|
||||
render/playbackcache.h
|
||||
render/playbackcache.cpp
|
||||
render/rendermodes.h
|
||||
render/shaderinfo.h
|
||||
render/videoparams.h
|
||||
render/videoparams.cpp
|
||||
PARENT_SCOPE
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#ifndef SHADERINFO_H
|
||||
#define SHADERINFO_H
|
||||
|
||||
#endif // SHADERINFO_H
|
||||
@@ -28,7 +28,6 @@ set(OLIVE_SOURCES
|
||||
render/backend/opengl/openglrenderfunctions.cpp
|
||||
render/backend/opengl/openglshader.h
|
||||
render/backend/opengl/openglshader.cpp
|
||||
render/backend/opengl/openglshadercache.h
|
||||
render/backend/opengl/opengltexture.h
|
||||
render/backend/opengl/opengltexture.cpp
|
||||
render/backend/opengl/opengltexturecache.h
|
||||
|
||||
@@ -142,9 +142,16 @@ QVariant OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, const Video
|
||||
frame_params.divider());
|
||||
}
|
||||
|
||||
PixelFormat::Format texture_fmt;
|
||||
if (PixelFormat::FormatHasAlphaChannel(frame_params.format())) {
|
||||
texture_fmt = PixelFormat::GetFormatWithAlphaChannel(params.format());
|
||||
} else {
|
||||
texture_fmt = PixelFormat::GetFormatWithoutAlphaChannel(params.format());
|
||||
}
|
||||
|
||||
VideoParams dest_params(frame_params.width(),
|
||||
frame_params.height(),
|
||||
params.format(),
|
||||
texture_fmt,
|
||||
frame_params.divider());
|
||||
|
||||
// Create destination texture
|
||||
@@ -175,6 +182,47 @@ QVariant OpenGLProxy::PreCachedFrameToValue(FramePtr frame)
|
||||
return QVariant::fromValue(texture_cache_.Get(ctx_, frame));
|
||||
}
|
||||
|
||||
OpenGLShaderPtr OpenGLProxy::ResolveShaderFromCache(const Node *node, const QByteArray &shader_id)
|
||||
{
|
||||
// Make a composite of the node ID and the shader ID (if applicable)
|
||||
QByteArray id = node->id().toUtf8();
|
||||
id.append(shader_id);
|
||||
OpenGLShaderPtr shader = shader_cache_.value(id);
|
||||
|
||||
if (!shader) {
|
||||
// Since we have shader code, compile it now
|
||||
ShaderCode code = node->GetShaderCode(shader_id);
|
||||
QString vert_code = code.vert_code();
|
||||
QString frag_code = code.frag_code();
|
||||
|
||||
if (frag_code.isEmpty() && vert_code.isEmpty()) {
|
||||
qWarning() << "No shader code found for" << node->id() << "- operation will be a no-op";
|
||||
}
|
||||
|
||||
if (frag_code.isEmpty()) {
|
||||
frag_code = OpenGLShader::CodeDefaultFragment();
|
||||
}
|
||||
|
||||
if (vert_code.isEmpty()) {
|
||||
vert_code = OpenGLShader::CodeDefaultVertex();
|
||||
}
|
||||
|
||||
shader = OpenGLShader::Create();
|
||||
if (shader
|
||||
&& shader->create()
|
||||
&& shader->addShaderFromSourceCode(QOpenGLShader::Fragment, frag_code)
|
||||
&& shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_code)
|
||||
&& shader->link()) {
|
||||
shader_cache_.insert(shader_id, shader);
|
||||
} else {
|
||||
qWarning() << "Failed to compile shader for" << node->id();
|
||||
shader = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
void OpenGLProxy::Close()
|
||||
{
|
||||
shader_cache_.clear();
|
||||
@@ -186,185 +234,145 @@ void OpenGLProxy::Close()
|
||||
}
|
||||
|
||||
QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
|
||||
const TimeRange &range,
|
||||
NodeValueDatabase &input_params,
|
||||
const VideoParams& params)
|
||||
const TimeRange &range,
|
||||
const ShaderJob &job,
|
||||
const VideoParams& params)
|
||||
{
|
||||
OpenGLShaderPtr shader = shader_cache_.value(node->ShaderID(input_params));
|
||||
// If this node is iterative, we'll pick up which input here
|
||||
GLuint iterative_input = 0;
|
||||
QList<GLuint> textures_to_bind;
|
||||
bool input_textures_have_alpha = false;
|
||||
|
||||
OpenGLShaderPtr shader = ResolveShaderFromCache(node, job.GetShaderID());
|
||||
|
||||
if (!shader) {
|
||||
// Since we have shader code, compile it now
|
||||
|
||||
QString vert_code = node->ShaderVertexCode(input_params);
|
||||
QString frag_code = node->ShaderFragmentCode(input_params);
|
||||
|
||||
if (frag_code.isEmpty()) {
|
||||
frag_code = OpenGLShader::CodeDefaultFragment();
|
||||
}
|
||||
|
||||
if (vert_code.isEmpty()) {
|
||||
vert_code = OpenGLShader::CodeDefaultVertex();
|
||||
}
|
||||
|
||||
shader = OpenGLShader::Create();
|
||||
shader->create();
|
||||
shader->addShaderFromSourceCode(QOpenGLShader::Fragment, frag_code);
|
||||
shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_code);
|
||||
shader->link();
|
||||
|
||||
shader_cache_.insert(node->id(), shader);
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
// Create the output textures
|
||||
QList<OpenGLTextureCache::ReferencePtr> dst_refs;
|
||||
dst_refs.append(texture_cache_.Get(ctx_, params));
|
||||
GLuint iterative_input = 0;
|
||||
|
||||
// If this node requires multiple iterations, get a texture for it too
|
||||
if (node->ShaderIterations() > 1 && node->ShaderIterativeInput()) {
|
||||
dst_refs.append(texture_cache_.Get(ctx_, params));
|
||||
}
|
||||
|
||||
// Lock the shader so no other thread interferes as we set parameters and draw (and we don't interfere with any others)
|
||||
shader->bind();
|
||||
|
||||
unsigned int input_texture_count = 0;
|
||||
NodeValueMap::const_iterator i;
|
||||
for (i=job.GetValues().constBegin(); i!=job.GetValues().constEnd(); i++) {
|
||||
// See if the shader has takes this parameter as an input
|
||||
int variable_location = shader->uniformLocation(i.key()->id());
|
||||
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (variable_location > -1) {
|
||||
// This variable is used in the shader, let's set it to our value
|
||||
// This variable is used in the shader, let's set it
|
||||
const QVariant& value = i.value().data();
|
||||
|
||||
NodeInput* input = static_cast<NodeInput*>(param);
|
||||
const NodeParam::DataType& data_type = (i.value().type() != NodeParam::kNone)
|
||||
? i.value().type()
|
||||
: i.key()->data_type();
|
||||
|
||||
// Get value from database at this input
|
||||
NodeValue meta_value = node->InputValueFromTable(input, input_params, false);
|
||||
const QVariant& value = meta_value.data();
|
||||
switch (data_type) {
|
||||
case NodeInput::kInt:
|
||||
shader->setUniformValue(variable_location, value.toInt());
|
||||
break;
|
||||
case NodeInput::kFloat:
|
||||
shader->setUniformValue(variable_location, value.toFloat());
|
||||
break;
|
||||
case NodeInput::kVec2:
|
||||
if (i.key()->IsArray()) {
|
||||
QVector<NodeValue> nv = value.value< QVector<NodeValue> >();
|
||||
QVector<QVector2D> a(nv.size());
|
||||
|
||||
NodeParam::DataType data_type;
|
||||
|
||||
if (meta_value.type() != NodeParam::kNone) {
|
||||
// Use value's data type
|
||||
data_type = meta_value.type();
|
||||
} else {
|
||||
// Fallback on null value, send the null to the parameter
|
||||
data_type = input->data_type();
|
||||
for (int j=0;j<a.size();j++) {
|
||||
a[j] = nv.at(j).data().value<QVector2D>();
|
||||
}
|
||||
|
||||
switch (data_type) {
|
||||
case NodeInput::kInt:
|
||||
shader->setUniformValue(variable_location, value.toInt());
|
||||
break;
|
||||
case NodeInput::kFloat:
|
||||
shader->setUniformValue(variable_location, value.toFloat());
|
||||
break;
|
||||
case NodeInput::kVec2:
|
||||
if (input->IsArray()) {
|
||||
NodeInputArray* array = static_cast<NodeInputArray*>(input);
|
||||
QVector<QVector2D> a(array->GetSize());
|
||||
shader->setUniformValueArray(variable_location, a.constData(), a.size());
|
||||
|
||||
for (int i=0;i<a.size();i++) {
|
||||
a[i] = input_params[array->At(i)].Get(NodeParam::kVec2).value<QVector2D>();
|
||||
}
|
||||
|
||||
shader->setUniformValueArray(variable_location, a.constData(), a.size());
|
||||
|
||||
int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(input->id()));
|
||||
if (count_location > -1) {
|
||||
shader->setUniformValue(count_location,
|
||||
array->GetSize());
|
||||
}
|
||||
|
||||
} else {
|
||||
shader->setUniformValue(variable_location, value.value<QVector2D>());
|
||||
}
|
||||
break;
|
||||
case NodeInput::kVec3:
|
||||
shader->setUniformValue(variable_location, value.value<QVector3D>());
|
||||
break;
|
||||
case NodeInput::kVec4:
|
||||
shader->setUniformValue(variable_location, value.value<QVector4D>());
|
||||
break;
|
||||
case NodeInput::kMatrix:
|
||||
shader->setUniformValue(variable_location, value.value<QMatrix4x4>());
|
||||
break;
|
||||
case NodeInput::kCombo:
|
||||
shader->setUniformValue(variable_location, value.value<int>());
|
||||
break;
|
||||
case NodeInput::kColor:
|
||||
{
|
||||
Color color = value.value<Color>();
|
||||
|
||||
shader->setUniformValue(variable_location, color.red(), color.green(), color.blue(), color.alpha());
|
||||
break;
|
||||
int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(i.key()->id()));
|
||||
if (count_location > -1) {
|
||||
shader->setUniformValue(count_location, a.size());
|
||||
}
|
||||
case NodeInput::kBoolean:
|
||||
shader->setUniformValue(variable_location, value.toBool());
|
||||
break;
|
||||
case NodeInput::kFootage:
|
||||
case NodeInput::kTexture:
|
||||
case NodeInput::kBuffer:
|
||||
{
|
||||
OpenGLTextureCache::ReferencePtr texture = value.value<OpenGLTextureCache::ReferencePtr>();
|
||||
} else {
|
||||
shader->setUniformValue(variable_location, value.value<QVector2D>());
|
||||
}
|
||||
break;
|
||||
case NodeInput::kVec3:
|
||||
shader->setUniformValue(variable_location, value.value<QVector3D>());
|
||||
break;
|
||||
case NodeInput::kVec4:
|
||||
shader->setUniformValue(variable_location, value.value<QVector4D>());
|
||||
break;
|
||||
case NodeInput::kMatrix:
|
||||
shader->setUniformValue(variable_location, value.value<QMatrix4x4>());
|
||||
break;
|
||||
case NodeInput::kCombo:
|
||||
shader->setUniformValue(variable_location, value.value<int>());
|
||||
break;
|
||||
case NodeInput::kColor:
|
||||
{
|
||||
Color color = value.value<Color>();
|
||||
|
||||
functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count);
|
||||
shader->setUniformValue(variable_location, color.red(), color.green(), color.blue(), color.alpha());
|
||||
break;
|
||||
}
|
||||
case NodeInput::kBoolean:
|
||||
shader->setUniformValue(variable_location, value.toBool());
|
||||
break;
|
||||
case NodeInput::kTexture:
|
||||
{
|
||||
OpenGLTextureCache::ReferencePtr texture = value.value<OpenGLTextureCache::ReferencePtr>();
|
||||
|
||||
GLuint tex_id = texture ? texture->texture()->texture() : 0;
|
||||
functions_->glBindTexture(GL_TEXTURE_2D, tex_id);
|
||||
|
||||
// Set value to bound texture
|
||||
shader->setUniformValue(variable_location, input_texture_count);
|
||||
|
||||
// Set enable flag if shader wants it
|
||||
int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(input->id()));
|
||||
if (enable_param_location > -1) {
|
||||
shader->setUniformValue(enable_param_location,
|
||||
tex_id > 0);
|
||||
}
|
||||
|
||||
if (tex_id > 0) {
|
||||
// Set texture resolution if shader wants it
|
||||
int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(input->id()));
|
||||
if (res_param_location > -1) {
|
||||
shader->setUniformValue(res_param_location,
|
||||
static_cast<GLfloat>(texture->texture()->width() * texture->texture()->divider()),
|
||||
static_cast<GLfloat>(texture->texture()->height() * texture->texture()->divider()));
|
||||
}
|
||||
}
|
||||
|
||||
// If this texture binding is the iterative input, set it here
|
||||
if (input == node->ShaderIterativeInput()) {
|
||||
iterative_input = input_texture_count;
|
||||
}
|
||||
|
||||
OpenGLRenderFunctions::PrepareToDraw(functions_);
|
||||
|
||||
input_texture_count++;
|
||||
break;
|
||||
}
|
||||
case NodeInput::kSamples:
|
||||
case NodeInput::kText:
|
||||
case NodeInput::kRational:
|
||||
case NodeInput::kFont:
|
||||
case NodeInput::kFile:
|
||||
case NodeInput::kDecimal:
|
||||
case NodeInput::kNumber:
|
||||
case NodeInput::kString:
|
||||
case NodeInput::kVector:
|
||||
case NodeInput::kNone:
|
||||
case NodeInput::kAny:
|
||||
break;
|
||||
if (texture) {
|
||||
if (PixelFormat::FormatHasAlphaChannel(texture->texture()->format())) {
|
||||
input_textures_have_alpha = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set value to bound texture
|
||||
shader->setUniformValue(variable_location, textures_to_bind.size());
|
||||
|
||||
// If this texture binding is the iterative input, set it here
|
||||
if (i.key() == job.GetIterativeInput()) {
|
||||
iterative_input = textures_to_bind.size();
|
||||
}
|
||||
|
||||
GLuint tex_id = texture ? texture->texture()->texture() : 0;
|
||||
textures_to_bind.append(tex_id);
|
||||
|
||||
// Set enable flag if shader wants it
|
||||
int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(i.key()->id()));
|
||||
if (enable_param_location > -1) {
|
||||
shader->setUniformValue(enable_param_location,
|
||||
tex_id > 0);
|
||||
}
|
||||
|
||||
if (tex_id > 0) {
|
||||
// Set texture resolution if shader wants it
|
||||
int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(i.key()->id()));
|
||||
if (res_param_location > -1) {
|
||||
shader->setUniformValue(res_param_location,
|
||||
static_cast<GLfloat>(texture->texture()->width() * texture->texture()->divider()),
|
||||
static_cast<GLfloat>(texture->texture()->height() * texture->texture()->divider()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NodeInput::kSamples:
|
||||
case NodeInput::kText:
|
||||
case NodeInput::kRational:
|
||||
case NodeInput::kFont:
|
||||
case NodeInput::kFile:
|
||||
case NodeInput::kDecimal:
|
||||
case NodeInput::kNumber:
|
||||
case NodeInput::kString:
|
||||
case NodeInput::kVector:
|
||||
case NodeInput::kShaderJob:
|
||||
case NodeInput::kSampleJob:
|
||||
case NodeInput::kFootage:
|
||||
case NodeInput::kBuffer:
|
||||
case NodeInput::kNone:
|
||||
case NodeInput::kAny:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up OpenGL parameters as necessary
|
||||
functions_->glViewport(0, 0, params.effective_width(), params.effective_height());
|
||||
|
||||
// Provide some standard args
|
||||
shader->setUniformValue("ove_resolution",
|
||||
static_cast<GLfloat>(params.width()),
|
||||
@@ -383,25 +391,65 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
|
||||
shader->setUniformValue("ove_tprog_in", static_cast<GLfloat>(transition_node->GetInProgress(range.in())));
|
||||
}
|
||||
|
||||
shader->release();
|
||||
|
||||
// Create the output textures
|
||||
PixelFormat::Format output_format = (input_textures_have_alpha || job.GetAlphaChannelRequired())
|
||||
? PixelFormat::GetFormatWithAlphaChannel(params.format())
|
||||
: PixelFormat::GetFormatWithoutAlphaChannel(params.format());
|
||||
VideoParams output_params(params.width(),
|
||||
params.height(),
|
||||
params.time_base(),
|
||||
output_format,
|
||||
params.divider());
|
||||
|
||||
int real_iteration_count;
|
||||
if (job.GetIterationCount() > 1 && job.GetIterativeInput()) {
|
||||
real_iteration_count = job.GetIterationCount();
|
||||
} else {
|
||||
real_iteration_count = 1;
|
||||
}
|
||||
|
||||
OpenGLTextureCache::ReferencePtr dst_refs[2];
|
||||
dst_refs[0] = texture_cache_.Get(ctx_, output_params);
|
||||
|
||||
// If this node requires multiple iterations, get a texture for it too
|
||||
if (real_iteration_count > 1) {
|
||||
dst_refs[1] = texture_cache_.Get(ctx_, output_params);
|
||||
}
|
||||
|
||||
// Some nodes use multiple iterations for optimization
|
||||
OpenGLTextureCache::ReferencePtr output_tex;
|
||||
OpenGLTextureCache::ReferencePtr input_tex, output_tex;
|
||||
|
||||
for (int iteration=0;iteration<node->ShaderIterations();iteration++) {
|
||||
// If this is not the first iteration, set the parameter that will receive the last iteration's texture
|
||||
OpenGLTextureCache::ReferencePtr source_tex = dst_refs.at((iteration+1)%dst_refs.size());
|
||||
OpenGLTextureCache::ReferencePtr destination_tex = dst_refs.at(iteration%dst_refs.size());
|
||||
// Set up OpenGL parameters as necessary
|
||||
functions_->glViewport(0, 0, params.effective_width(), params.effective_height());
|
||||
|
||||
// Bind all textures
|
||||
for (int i=0; i<textures_to_bind.size(); i++) {
|
||||
functions_->glActiveTexture(GL_TEXTURE0 + i);
|
||||
functions_->glBindTexture(GL_TEXTURE_2D, textures_to_bind.at(i));
|
||||
OpenGLRenderFunctions::PrepareToDraw(functions_);
|
||||
}
|
||||
|
||||
for (int iteration=0; iteration<real_iteration_count; iteration++) {
|
||||
// Set iteration number
|
||||
shader->bind();
|
||||
shader->setUniformValue("ove_iteration", iteration);
|
||||
shader->release();
|
||||
|
||||
if (iteration > 0) {
|
||||
// Replace iterative input
|
||||
if (iteration == 0) {
|
||||
output_tex = dst_refs[0];
|
||||
} else {
|
||||
input_tex = dst_refs[(iteration+1)%2];
|
||||
output_tex = dst_refs[iteration%2];
|
||||
|
||||
functions_->glActiveTexture(GL_TEXTURE0 + iterative_input);
|
||||
functions_->glBindTexture(GL_TEXTURE_2D, source_tex->texture()->texture());
|
||||
functions_->glBindTexture(GL_TEXTURE_2D, input_tex->texture()->texture());
|
||||
OpenGLRenderFunctions::PrepareToDraw(functions_);
|
||||
}
|
||||
|
||||
buffer_.Attach(destination_tex->texture(), true);
|
||||
buffer_.Attach(output_tex->texture());
|
||||
buffer_.Bind();
|
||||
|
||||
// Blit this texture through this shader
|
||||
@@ -409,22 +457,14 @@ QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
|
||||
|
||||
buffer_.Release();
|
||||
buffer_.Detach();
|
||||
|
||||
// Update output reference to the last texture we wrote to
|
||||
output_tex = destination_tex;
|
||||
}
|
||||
|
||||
// Release any textures we bound before
|
||||
while (input_texture_count > 0) {
|
||||
input_texture_count--;
|
||||
|
||||
// Release texture here
|
||||
functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count);
|
||||
for (int i=textures_to_bind.size()-1; i>=0; i--) {
|
||||
functions_->glActiveTexture(GL_TEXTURE0 + i);
|
||||
functions_->glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
shader->release();
|
||||
|
||||
return QVariant::fromValue(output_tex);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
#include "node/value.h"
|
||||
#include "openglcolorprocessor.h"
|
||||
#include "openglframebuffer.h"
|
||||
#include "openglshadercache.h"
|
||||
#include "opengltexturecache.h"
|
||||
#include "render/shaderinfo.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
public slots:
|
||||
QVariant RunNodeAccelerated(const OLIVE_NAMESPACE::Node *node,
|
||||
const OLIVE_NAMESPACE::TimeRange &range,
|
||||
OLIVE_NAMESPACE::NodeValueDatabase &input_params,
|
||||
const OLIVE_NAMESPACE::ShaderJob &job,
|
||||
const OLIVE_NAMESPACE::VideoParams ¶ms);
|
||||
|
||||
void TextureToBuffer(const QVariant& texture,
|
||||
@@ -84,6 +84,8 @@ public slots:
|
||||
QVariant PreCachedFrameToValue(OLIVE_NAMESPACE::FramePtr frame);
|
||||
|
||||
private:
|
||||
OpenGLShaderPtr ResolveShaderFromCache(const Node* node, const QByteArray& shader_id);
|
||||
|
||||
QOpenGLContext* ctx_;
|
||||
QOffscreenSurface surface_;
|
||||
|
||||
@@ -95,7 +97,7 @@ private:
|
||||
|
||||
OpenGLShaderPtr copy_pipeline_;
|
||||
|
||||
OpenGLShaderCache shader_cache_;
|
||||
QHash<QByteArray, OpenGLShaderPtr> shader_cache_;
|
||||
|
||||
OpenGLTextureCache texture_cache_;
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ QVariant OpenGLWorker::CachedFrameToTexture(FramePtr frame) const
|
||||
return value;
|
||||
}
|
||||
|
||||
QVariant OpenGLWorker::ProcessShader(const Node *node, const TimeRange &range, NodeValueDatabase &input_params)
|
||||
QVariant OpenGLWorker::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job)
|
||||
{
|
||||
QVariant value;
|
||||
|
||||
@@ -77,10 +77,15 @@ QVariant OpenGLWorker::ProcessShader(const Node *node, const TimeRange &range, N
|
||||
Q_RETURN_ARG(QVariant, value),
|
||||
OLIVE_NS_CONST_ARG(Node*, node),
|
||||
OLIVE_NS_CONST_ARG(TimeRange&, range),
|
||||
OLIVE_NS_ARG(NodeValueDatabase&, input_params),
|
||||
OLIVE_NS_CONST_ARG(ShaderJob&, job),
|
||||
OLIVE_NS_CONST_ARG(VideoParams&, video_params()));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool OpenGLWorker::TextureHasAlpha(const QVariant &v) const
|
||||
{
|
||||
return PixelFormat::FormatHasAlphaChannel(v.value<OpenGLTextureCache::ReferencePtr>()->texture()->format());
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -38,7 +38,9 @@ protected:
|
||||
|
||||
virtual QVariant CachedFrameToTexture(FramePtr frame) const override;
|
||||
|
||||
virtual QVariant ProcessShader(const Node *node, const TimeRange &range, NodeValueDatabase &input_params) override;
|
||||
virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override;
|
||||
|
||||
virtual bool TextureHasAlpha(const QVariant& v) const override;
|
||||
|
||||
private:
|
||||
OpenGLProxy* proxy_;
|
||||
|
||||
+114
-151
@@ -47,8 +47,19 @@ void RenderWorker::RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, con
|
||||
|
||||
QVariant texture = table.Get(NodeParam::kTexture);
|
||||
|
||||
PixelFormat::Format output_format;
|
||||
if (!texture.isNull() && TextureHasAlpha(texture)) {
|
||||
output_format = PixelFormat::GetFormatWithAlphaChannel(video_params_.format());
|
||||
} else {
|
||||
output_format = PixelFormat::GetFormatWithoutAlphaChannel(video_params_.format());
|
||||
}
|
||||
|
||||
FramePtr frame = Frame::Create();
|
||||
frame->set_video_params(video_params_);
|
||||
frame->set_video_params(VideoParams(video_params_.width(),
|
||||
video_params_.height(),
|
||||
video_params_.time_base(),
|
||||
output_format,
|
||||
video_params_.divider()));
|
||||
frame->set_timestamp(time);
|
||||
frame->allocate();
|
||||
|
||||
@@ -172,10 +183,11 @@ NodeValueTable RenderWorker::GenerateBlockTable(const TrackOutput *track, const
|
||||
}
|
||||
}
|
||||
|
||||
QVariant RenderWorker::ProcessSamples(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in)
|
||||
QVariant RenderWorker::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob& job)
|
||||
{
|
||||
// Copy database so we can make some temporary modifications to it
|
||||
NodeValueDatabase input_params = input_params_in;
|
||||
// FIX THIS CODE:
|
||||
return QVariant();
|
||||
/*
|
||||
NodeInput* sample_input = node->ProcessesSamplesFrom(input_params);
|
||||
|
||||
// Try to find the sample buffer in the table
|
||||
@@ -225,134 +237,20 @@ QVariant RenderWorker::ProcessSamples(const Node *node, const TimeRange &range,
|
||||
}
|
||||
|
||||
return QVariant::fromValue(output_buffer);
|
||||
*/
|
||||
}
|
||||
|
||||
void RenderWorker::ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in, NodeValueTable &output_params)
|
||||
QVariant RenderWorker::GetCachedFrame(const Node* node, const rational& time)
|
||||
{
|
||||
// Convert footage to image/sample buffers
|
||||
if (node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) {
|
||||
QByteArray hash = RenderBackend::HashNode(node, video_params(), range.in());
|
||||
QByteArray hash = RenderBackend::HashNode(node, video_params(), time);
|
||||
|
||||
QString fn = FrameHashCache::CachePathName(hash);
|
||||
|
||||
if (QFileInfo::exists(fn)) {
|
||||
FramePtr f = FrameHashCache::LoadCacheFrame(hash);
|
||||
|
||||
QVariant cached = CachedFrameToTexture(f);
|
||||
|
||||
if (!cached.isNull()) {
|
||||
output_params.Push(NodeParam::kTexture, cached, node);
|
||||
|
||||
// No more to do here
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<NodeInput*> inputs = node->GetInputsIncludingArrays();
|
||||
foreach (NodeInput* input, inputs) {
|
||||
if (input->data_type() == NodeParam::kFootage) {
|
||||
TimeRange input_time = node->InputTimeAdjustment(input, range);
|
||||
|
||||
StreamPtr stream = ResolveStreamFromInput(input);
|
||||
|
||||
if (stream) {
|
||||
QVariant v = ProcessFootage(stream, input_time);
|
||||
|
||||
if (!v.isNull()) {
|
||||
if (stream->type() == Stream::kVideo || stream->type() == Stream::kImage) {
|
||||
output_params.Push(NodeParam::kTexture, v, node);
|
||||
} else if (stream->type() == Stream::kAudio) {
|
||||
output_params.Push(NodeParam::kSamples, v, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if node has a shader
|
||||
if (node->GetCapabilities(input_params_in) & Node::kShader) {
|
||||
QVariant v = ProcessShader(node, range, input_params_in);
|
||||
|
||||
if (!v.isNull()) {
|
||||
output_params.Push(NodeParam::kTexture, v, node);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if node processes samples
|
||||
if (node->GetCapabilities(input_params_in) & Node::kSampleProcessor) {
|
||||
QVariant v = ProcessSamples(node, range, input_params_in);
|
||||
|
||||
if (!v.isNull()) {
|
||||
output_params.Push(NodeParam::kSamples, v, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant RenderWorker::GetDataFromStream(StreamPtr stream, const TimeRange &input_time)
|
||||
{
|
||||
DecoderPtr decoder = ResolveDecoderFromInput(stream);
|
||||
|
||||
if (decoder) {
|
||||
if (stream->type() == Stream::kVideo || stream->type() == Stream::kImage) {
|
||||
|
||||
FramePtr frame = decoder->RetrieveVideo(input_time.in(),
|
||||
video_params().divider());
|
||||
|
||||
if (frame) {
|
||||
// Return a texture from the derived class
|
||||
return FootageFrameToTexture(stream, frame);
|
||||
}
|
||||
|
||||
} else if (stream->type() == Stream::kAudio) {
|
||||
|
||||
// See if we have a conformed version of this audio
|
||||
if (!decoder->HasConformedVersion(audio_params())) {
|
||||
|
||||
// If not, check what audio mode we're in
|
||||
if (audio_mode_is_preview_) {
|
||||
|
||||
// For preview, we report the conform is missing and finish the render without it
|
||||
// temporarily. The backend that picks up this signal will recache this section once the
|
||||
// conform is available.
|
||||
emit AudioConformUnavailable(decoder->stream(),
|
||||
audio_render_time_,
|
||||
input_time.out(),
|
||||
audio_params());
|
||||
|
||||
} else {
|
||||
|
||||
// For online rendering/export, it's a waste of time to render the audio until we have
|
||||
// all we need, so we try to handle the conform ourselves
|
||||
AudioStreamPtr as = std::static_pointer_cast<AudioStream>(stream);
|
||||
|
||||
// Check if any other threads are conforming this audio
|
||||
if (as->try_start_conforming(audio_params())) {
|
||||
|
||||
// If not, conform it ourselves
|
||||
decoder->ConformAudio(&IsCancelled(), audio_params());
|
||||
|
||||
} else {
|
||||
|
||||
// If another thread is conforming already, hackily try to wait until it's done.
|
||||
do {
|
||||
QThread::msleep(1000);
|
||||
} while (!as->has_conformed_version(audio_params()) && !IsCancelled());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (decoder->HasConformedVersion(audio_params())) {
|
||||
SampleBufferPtr frame = decoder->RetrieveAudio(input_time.in(), input_time.length(),
|
||||
audio_params());
|
||||
|
||||
if (frame) {
|
||||
return QVariant::fromValue(frame);
|
||||
}
|
||||
}
|
||||
return CachedFrameToTexture(f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,52 +280,117 @@ DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream)
|
||||
return decoder;
|
||||
}
|
||||
|
||||
QVariant RenderWorker::ProcessFootage(StreamPtr stream, const TimeRange &input_time)
|
||||
QVariant RenderWorker::ProcessVideoFootage(StreamPtr stream, const rational &input_time)
|
||||
{
|
||||
if (stream->type() == Stream::kVideo || stream->type() == Stream::kImage) {
|
||||
ImageStreamPtr video_stream = std::static_pointer_cast<ImageStream>(stream);
|
||||
rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time;
|
||||
QString colorspace_match = video_stream->get_colorspace_match_string();
|
||||
|
||||
ImageStreamPtr video_stream = std::static_pointer_cast<ImageStream>(stream);
|
||||
rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time.in();
|
||||
QString colorspace_match = video_stream->get_colorspace_match_string();
|
||||
QVariant value;
|
||||
bool found_cache = false;
|
||||
|
||||
QVariant value;
|
||||
bool found_cache = false;
|
||||
if (still_image_cache_.contains(stream.get())) {
|
||||
const CachedStill& cs = still_image_cache_[stream.get()];
|
||||
|
||||
if (still_image_cache_.contains(stream.get())) {
|
||||
const CachedStill& cs = still_image_cache_[stream.get()];
|
||||
if (cs.colorspace == colorspace_match
|
||||
&& cs.alpha_is_associated == video_stream->premultiplied_alpha()
|
||||
&& cs.divider == video_params_.divider()
|
||||
&& cs.time == time_match) {
|
||||
value = cs.texture;
|
||||
found_cache = true;
|
||||
} else {
|
||||
still_image_cache_.remove(stream.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (cs.colorspace == colorspace_match
|
||||
&& cs.alpha_is_associated == video_stream->premultiplied_alpha()
|
||||
&& cs.divider == video_params_.divider()
|
||||
&& cs.time == time_match) {
|
||||
value = cs.texture;
|
||||
found_cache = true;
|
||||
if (!found_cache) {
|
||||
|
||||
DecoderPtr decoder = ResolveDecoderFromInput(stream);
|
||||
|
||||
if (decoder) {
|
||||
FramePtr frame = decoder->RetrieveVideo(input_time,
|
||||
video_params().divider());
|
||||
|
||||
if (frame) {
|
||||
// Return a texture from the derived class
|
||||
value = FootageFrameToTexture(stream, frame);
|
||||
|
||||
if (value.isNull()) {
|
||||
qDebug() << "Texture from derivative was blank";
|
||||
} else {
|
||||
// Put this into the image cache instead
|
||||
still_image_cache_.insert(stream.get(), {value,
|
||||
colorspace_match,
|
||||
video_stream->premultiplied_alpha(),
|
||||
video_params_.divider(),
|
||||
time_match});
|
||||
}
|
||||
} else {
|
||||
still_image_cache_.remove(stream.get());
|
||||
qDebug() << "Frame from decoder was blank";
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_cache) {
|
||||
}
|
||||
|
||||
value = GetDataFromStream(stream, input_time);
|
||||
return value;
|
||||
}
|
||||
|
||||
still_image_cache_.insert(stream.get(), {value,
|
||||
colorspace_match,
|
||||
video_stream->premultiplied_alpha(),
|
||||
video_params_.divider(),
|
||||
time_match});
|
||||
QVariant RenderWorker::ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time)
|
||||
{
|
||||
QVariant value;
|
||||
|
||||
DecoderPtr decoder = ResolveDecoderFromInput(stream);
|
||||
|
||||
if (decoder) {
|
||||
// See if we have a conformed version of this audio
|
||||
if (!decoder->HasConformedVersion(audio_params())) {
|
||||
|
||||
// If not, check what audio mode we're in
|
||||
if (audio_mode_is_preview_) {
|
||||
|
||||
// For preview, we report the conform is missing and finish the render without it
|
||||
// temporarily. The backend that picks up this signal will recache this section once the
|
||||
// conform is available.
|
||||
emit AudioConformUnavailable(decoder->stream(),
|
||||
audio_render_time_,
|
||||
input_time.out(),
|
||||
audio_params());
|
||||
|
||||
} else {
|
||||
|
||||
// For online rendering/export, it's a waste of time to render the audio until we have
|
||||
// all we need, so we try to handle the conform ourselves
|
||||
AudioStreamPtr as = std::static_pointer_cast<AudioStream>(stream);
|
||||
|
||||
// Check if any other threads are conforming this audio
|
||||
if (as->try_start_conforming(audio_params())) {
|
||||
|
||||
// If not, conform it ourselves
|
||||
decoder->ConformAudio(&IsCancelled(), audio_params());
|
||||
|
||||
} else {
|
||||
|
||||
// If another thread is conforming already, hackily try to wait until it's done.
|
||||
do {
|
||||
QThread::msleep(1000);
|
||||
} while (!as->has_conformed_version(audio_params()) && !IsCancelled());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
} else if (stream->type() == Stream::kAudio) {
|
||||
|
||||
return GetDataFromStream(stream, input_time);
|
||||
if (decoder->HasConformedVersion(audio_params())) {
|
||||
SampleBufferPtr frame = decoder->RetrieveAudio(input_time.in(), input_time.length(),
|
||||
audio_params());
|
||||
|
||||
if (frame) {
|
||||
value = QVariant::fromValue(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
return value;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -120,9 +120,15 @@ protected:
|
||||
|
||||
virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange &range) override;
|
||||
|
||||
virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in, NodeValueTable &output_params) override;
|
||||
virtual QVariant ProcessVideoFootage(StreamPtr stream, const rational &input_time) override;
|
||||
|
||||
virtual QVariant ProcessShader(const Node *node, const TimeRange &range, NodeValueDatabase &input_params) = 0;
|
||||
virtual QVariant ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) override;
|
||||
|
||||
virtual QVariant ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) override;
|
||||
|
||||
virtual QVariant GetCachedFrame(const Node *node, const rational &time) override;
|
||||
|
||||
virtual bool TextureHasAlpha(const QVariant& v) const = 0;
|
||||
|
||||
const VideoParams& video_params() const
|
||||
{
|
||||
@@ -148,14 +154,8 @@ signals:
|
||||
void WaveformGenerated(OLIVE_NAMESPACE::TrackOutput* track, OLIVE_NAMESPACE::AudioVisualWaveform samples, OLIVE_NAMESPACE::TimeRange start);
|
||||
|
||||
private:
|
||||
QVariant ProcessSamples(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in);
|
||||
|
||||
QVariant GetDataFromStream(StreamPtr stream, const TimeRange& input_time);
|
||||
|
||||
DecoderPtr ResolveDecoderFromInput(StreamPtr stream);
|
||||
|
||||
QVariant ProcessFootage(StreamPtr stream, const TimeRange &input_time);
|
||||
|
||||
RenderBackend* parent_;
|
||||
|
||||
VideoParams video_params_;
|
||||
|
||||
@@ -338,7 +338,9 @@ bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const V
|
||||
header.channels().insert("R", Imf::Channel(pix_type));
|
||||
header.channels().insert("G", Imf::Channel(pix_type));
|
||||
header.channels().insert("B", Imf::Channel(pix_type));
|
||||
header.channels().insert("A", Imf::Channel(pix_type));
|
||||
if (PixelFormat::FormatHasAlphaChannel(vparam.format())) {
|
||||
header.channels().insert("A", Imf::Channel(pix_type));
|
||||
}
|
||||
|
||||
header.compression() = Imf::DWAA_COMPRESSION;
|
||||
header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f));
|
||||
@@ -347,14 +349,16 @@ bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const V
|
||||
|
||||
int bpc = PixelFormat::BytesPerChannel(vparam.format());
|
||||
|
||||
size_t xs = kRGBAChannels * bpc;
|
||||
size_t ys = vparam.effective_width() * kRGBAChannels * bpc;
|
||||
size_t xs = PixelFormat::ChannelCount(vparam.format()) * bpc;
|
||||
size_t ys = vparam.effective_width() * PixelFormat::ChannelCount(vparam.format()) * bpc;
|
||||
|
||||
Imf::FrameBuffer framebuffer;
|
||||
framebuffer.insert("R", Imf::Slice(pix_type, data, xs, ys));
|
||||
framebuffer.insert("G", Imf::Slice(pix_type, data + bpc, xs, ys));
|
||||
framebuffer.insert("B", Imf::Slice(pix_type, data + 2*bpc, xs, ys));
|
||||
framebuffer.insert("A", Imf::Slice(pix_type, data + 3*bpc, xs, ys));
|
||||
if (PixelFormat::FormatHasAlphaChannel(vparam.format())) {
|
||||
framebuffer.insert("A", Imf::Slice(pix_type, data + 3*bpc, xs, ys));
|
||||
}
|
||||
out.setFrameBuffer(framebuffer);
|
||||
|
||||
out.writePixels(vparam.effective_height());
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
#ifndef SHADERINFO_H
|
||||
#define SHADERINFO_H
|
||||
|
||||
#include "node/input.h"
|
||||
#include "node/inputarray.h"
|
||||
#include "node/value.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
using NodeValueMap = QHash<NodeInput*, NodeValue>;
|
||||
|
||||
class AcceleratedJob {
|
||||
public:
|
||||
AcceleratedJob() = default;
|
||||
|
||||
NodeValue GetValue(NodeInput* input) const
|
||||
{
|
||||
return value_map_.value(input);
|
||||
}
|
||||
|
||||
void InsertValue(NodeInput* input, NodeValueDatabase& value)
|
||||
{
|
||||
if (input->IsArray()) {
|
||||
NodeInputArray* array = static_cast<NodeInputArray*>(input);
|
||||
QVector<NodeValue> values(array->GetSize());
|
||||
|
||||
for (int j=0;j<array->GetSize();j++) {
|
||||
NodeInput* subparam = array->At(j);
|
||||
|
||||
values[j] = value[subparam].TakeWithMeta(subparam->data_type());
|
||||
}
|
||||
|
||||
value_map_.insert(input, NodeValue(NodeParam::kVec2, QVariant::fromValue(values), input->parentNode()));
|
||||
} else {
|
||||
value_map_.insert(input, value[input].TakeWithMeta(input->data_type()));
|
||||
}
|
||||
}
|
||||
|
||||
void InsertValue(NodeInput* input, const NodeValue& value)
|
||||
{
|
||||
value_map_.insert(input, value);
|
||||
}
|
||||
|
||||
const NodeValueMap &GetValues() const
|
||||
{
|
||||
return value_map_;
|
||||
}
|
||||
|
||||
private:
|
||||
NodeValueMap value_map_;
|
||||
|
||||
};
|
||||
|
||||
class SampleJob : public AcceleratedJob {
|
||||
public:
|
||||
SampleJob(NodeInput* from = nullptr) :
|
||||
from_(from)
|
||||
{
|
||||
}
|
||||
|
||||
NodeInput* from() const
|
||||
{
|
||||
return from_;
|
||||
}
|
||||
|
||||
private:
|
||||
NodeInput* from_;
|
||||
|
||||
};
|
||||
|
||||
class ShaderJob : public AcceleratedJob {
|
||||
public:
|
||||
ShaderJob()
|
||||
{
|
||||
iterations_ = 1;
|
||||
iterative_input_ = nullptr;
|
||||
alpha_channel_required_ = false;
|
||||
}
|
||||
|
||||
const QByteArray& GetShaderID() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
void SetShaderID(const QByteArray& id)
|
||||
{
|
||||
id_ = id;
|
||||
}
|
||||
|
||||
void SetIterations(int iterations, NodeInput* iterative_input)
|
||||
{
|
||||
iterations_ = iterations;
|
||||
iterative_input_ = iterative_input;
|
||||
}
|
||||
|
||||
int GetIterationCount() const
|
||||
{
|
||||
return iterations_;
|
||||
}
|
||||
|
||||
NodeInput* GetIterativeInput() const
|
||||
{
|
||||
return iterative_input_;
|
||||
}
|
||||
|
||||
bool GetAlphaChannelRequired() const
|
||||
{
|
||||
return alpha_channel_required_;
|
||||
}
|
||||
|
||||
void SetAlphaChannelRequired(bool e)
|
||||
{
|
||||
alpha_channel_required_ = e;
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray id_;
|
||||
|
||||
int iterations_;
|
||||
|
||||
NodeInput* iterative_input_;
|
||||
|
||||
bool alpha_channel_required_;
|
||||
|
||||
};
|
||||
|
||||
class ShaderCode {
|
||||
public:
|
||||
ShaderCode(const QString& frag_code, const QString& vert_code) :
|
||||
frag_code_(frag_code),
|
||||
vert_code_(vert_code)
|
||||
{
|
||||
}
|
||||
|
||||
const QString& frag_code() const
|
||||
{
|
||||
return frag_code_;
|
||||
}
|
||||
|
||||
const QString& vert_code() const
|
||||
{
|
||||
return vert_code_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString frag_code_;
|
||||
|
||||
QString vert_code_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
Q_DECLARE_METATYPE(OLIVE_NAMESPACE::ShaderJob)
|
||||
Q_DECLARE_METATYPE(OLIVE_NAMESPACE::SampleJob)
|
||||
|
||||
#endif // SHADERINFO_H
|
||||
Reference in New Issue
Block a user