began rewrite of renderer inner workings

This commit is contained in:
itsmattkc
2020-11-06 20:18:10 +11:00
parent 60eb71cb4c
commit 13dd31d5be
36 changed files with 1299 additions and 2373 deletions
-6
View File
@@ -217,11 +217,6 @@ public:
return waveform_;
}
QMutex* waveform_lock()
{
return &waveform_lock_;
}
static const double kTrackHeightDefault;
static const double kTrackHeightMinimum;
static const double kTrackHeightInterval;
@@ -297,7 +292,6 @@ private:
bool locked_;
AudioVisualWaveform waveform_;
QMutex waveform_lock_;
private slots:
void BlockConnected(NodeEdgePtr edge);
+2
View File
@@ -46,6 +46,8 @@ set(OLIVE_SOURCES
render/rendermanager.h
render/rendermanager.cpp
render/rendermodes.h
render/renderprocessor.h
render/renderprocessor.cpp
render/shaderinfo.h
render/videoparams.h
render/videoparams.cpp
+4
View File
@@ -18,5 +18,9 @@ add_subdirectory(opengl)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
render/backend/rendercontext.cpp
render/backend/rendercontext.h
render/backend/rendercontextthreadwrapper.cpp
render/backend/rendercontextthreadwrapper.h
PARENT_SCOPE
)
+2 -14
View File
@@ -16,19 +16,7 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
render/backend/opengl/openglcolorprocessor.h
render/backend/opengl/openglcolorprocessor.cpp
render/backend/opengl/openglframebuffer.h
render/backend/opengl/openglframebuffer.cpp
render/backend/opengl/openglproxy.h
render/backend/opengl/openglproxy.cpp
render/backend/opengl/openglrenderfunctions.h
render/backend/opengl/openglrenderfunctions.cpp
render/backend/opengl/openglshader.h
render/backend/opengl/openglshader.cpp
render/backend/opengl/opengltexture.h
render/backend/opengl/opengltexture.cpp
render/backend/opengl/opengltexturecache.h
render/backend/opengl/opengltexturecache.cpp
render/backend/opengl/openglcontext.cpp
render/backend/opengl/openglcontext.h
PARENT_SCOPE
)
@@ -1,90 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "openglcolorprocessor.h"
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include "openglrenderfunctions.h"
OLIVE_NAMESPACE_ENTER
void OpenGLColorProcessor::Enable(QOpenGLContext *context, bool alpha_is_associated)
{
if (IsEnabled()) {
return;
}
context_ = context;
pipeline_ = OpenGLShader::CreateOCIO(context_,
ocio_lut_,
GetProcessor(),
alpha_is_associated);
connect(context_, &QOpenGLContext::aboutToBeDestroyed, this, &OpenGLColorProcessor::ClearTexture, Qt::DirectConnection);
}
bool OpenGLColorProcessor::IsEnabled() const
{
return ocio_lut_;
}
OpenGLShaderPtr OpenGLColorProcessor::pipeline() const
{
return pipeline_;
}
void OpenGLColorProcessor::ProcessOpenGL(bool flipped, const QMatrix4x4& matrix)
{
OpenGLRenderFunctions::OCIOBlit(pipeline_, ocio_lut_, flipped, matrix);
}
void OpenGLColorProcessor::ClearTexture()
{
if (IsEnabled()) {
// Clean up OCIO LUT texture and shader
context_->functions()->glDeleteTextures(1, &ocio_lut_);
disconnect(context_, &QOpenGLContext::aboutToBeDestroyed, this, &OpenGLColorProcessor::ClearTexture);
ocio_lut_ = 0;
pipeline_ = nullptr;
}
}
OpenGLColorProcessor::OpenGLColorProcessor(ColorManager* config, const QString &source_space, const ColorTransform &dest_space) :
ColorProcessor(config, source_space, dest_space),
ocio_lut_(0)
{
}
OpenGLColorProcessor::~OpenGLColorProcessor()
{
ClearTexture();
}
OpenGLColorProcessorPtr OpenGLColorProcessor::Create(ColorManager *config, const QString &source_space, const ColorTransform &dest_space)
{
return std::make_shared<OpenGLColorProcessor>(config, source_space, dest_space);
}
OLIVE_NAMESPACE_EXIT
@@ -1,69 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OPENGLCOLORPROCESSOR_H
#define OPENGLCOLORPROCESSOR_H
#include "openglshader.h"
#include "render/colorprocessor.h"
OLIVE_NAMESPACE_ENTER
class OpenGLColorProcessor;
using OpenGLColorProcessorPtr = std::shared_ptr<OpenGLColorProcessor>;
class OpenGLColorProcessor : public QObject, public ColorProcessor
{
Q_OBJECT
public:
OpenGLColorProcessor(ColorManager *config,
const QString& input,
const ColorTransform& dest);
virtual ~OpenGLColorProcessor() override;
static OpenGLColorProcessorPtr Create(ColorManager* config,
const QString& input,
const ColorTransform& dest);
void Enable(QOpenGLContext* context, bool alpha_is_associated);
bool IsEnabled() const;
OpenGLShaderPtr pipeline() const;
void ProcessOpenGL(bool flipped = false, const QMatrix4x4& matrix = QMatrix4x4());
private:
QOpenGLContext* context_;
GLuint ocio_lut_;
OpenGLShaderPtr pipeline_;
private slots:
void ClearTexture();
};
using OpenGLColorProcessorCache = QHash<QString, OpenGLColorProcessorPtr>;
OLIVE_NAMESPACE_EXIT
#endif // OPENGLCOLORPROCESSOR_H
+195
View File
@@ -0,0 +1,195 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "openglcontext.h"
#include <QDebug>
OLIVE_NAMESPACE_ENTER
OpenGLContext::OpenGLContext(QObject* parent) :
RenderContext(parent)
{
}
OpenGLContext::~OpenGLContext()
{
}
bool OpenGLContext::Init()
{
surface_.create();
context_ = new QOpenGLContext();
if (!context_->create()) {
qCritical() << "Failed to create OpenGL context";
return false;
}
context_->moveToThread(this->thread());
return true;
}
void OpenGLContext::PostInit()
{
// Make context current on that surface
if (!context_->makeCurrent(&surface_)) {
qCritical() << "Failed to makeCurrent() on offscreen surface in thread" << thread();
return;
}
// Store OpenGL functions instance
functions_ = context_->functions();
functions_->glBlendFunc(GL_ONE, GL_ZERO);
}
void OpenGLContext::Destroy()
{
delete context_;
surface_.destroy();
}
QVariant OpenGLContext::CreateTexture(const VideoParams &p, void *data, int linesize)
{
GLuint texture;
functions_->glGenTextures(1, &texture);
texture_params_.insert(texture, p);
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
functions_->glTexImage2D(GL_TEXTURE_2D, 0, GetInternalFormat(p.format()),
p.width(), p.height(), 0, GetPixelFormat(p.format()),
GetPixelType(p.format()), data);
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
return texture;
}
void OpenGLContext::DestroyTexture(QVariant texture)
{
GLuint t = texture.value<GLuint>();
functions_->glDeleteTextures(1, &t);
texture_params_.remove(t);
}
void OpenGLContext::UploadToTexture(QVariant texture, void *data, int linesize)
{
GLuint t = texture.value<GLuint>();
const VideoParams& p = texture_params_.value(t);
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
functions_->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
p.effective_width(), p.effective_height(),
GetPixelFormat(p.format()), GetPixelType(p.format()),
data);
functions_->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
void OpenGLContext::DownloadFromTexture(QVariant texture, void *data, int linesize)
{
GLuint t = texture.value<GLuint>();
const VideoParams& p = texture_params_.value(t);
functions_->glPixelStorei(GL_PACK_ROW_LENGTH, linesize);
functions_->glReadPixels(0,
0,
p.width(),
p.height(),
GetPixelFormat(p.format()),
GetPixelType(p.format()),
data);
functions_->glPixelStorei(GL_PACK_ROW_LENGTH, 0);
}
VideoParams OpenGLContext::GetParamsFromTexture(QVariant texture)
{
GLuint t = texture.value<GLuint>();
return texture_params_.value(t);
}
GLint OpenGLContext::GetInternalFormat(PixelFormat::Format format)
{
switch (format) {
case PixelFormat::PIX_FMT_RGB8:
return GL_RGB8;
case PixelFormat::PIX_FMT_RGBA8:
return GL_RGBA8;
case PixelFormat::PIX_FMT_RGB16U:
return GL_RGB16;
case PixelFormat::PIX_FMT_RGBA16U:
return GL_RGBA16;
case PixelFormat::PIX_FMT_RGB16F:
return GL_RGB16F;
case PixelFormat::PIX_FMT_RGBA16F:
return GL_RGBA16F;
case PixelFormat::PIX_FMT_RGB32F:
return GL_RGB32F;
case PixelFormat::PIX_FMT_RGBA32F:
return GL_RGBA32F;
case PixelFormat::PIX_FMT_INVALID:
case PixelFormat::PIX_FMT_COUNT:
break;
}
return GL_INVALID_VALUE;
}
GLenum OpenGLContext::GetPixelFormat(PixelFormat::Format format)
{
if (PixelFormat::FormatHasAlphaChannel(format)) {
return GL_RGBA;
} else {
return GL_RGB;
}
}
GLenum OpenGLContext::GetPixelType(PixelFormat::Format format)
{
switch (format) {
case PixelFormat::PIX_FMT_RGB8:
case PixelFormat::PIX_FMT_RGBA8:
return GL_UNSIGNED_BYTE;
case PixelFormat::PIX_FMT_RGB16U:
case PixelFormat::PIX_FMT_RGBA16U:
return GL_UNSIGNED_SHORT;
case PixelFormat::PIX_FMT_RGB16F:
case PixelFormat::PIX_FMT_RGBA16F:
return GL_HALF_FLOAT;
case PixelFormat::PIX_FMT_RGB32F:
case PixelFormat::PIX_FMT_RGBA32F:
return GL_FLOAT;
case PixelFormat::PIX_FMT_INVALID:
case PixelFormat::PIX_FMT_COUNT:
break;
}
return GL_INVALID_VALUE;
}
OLIVE_NAMESPACE_EXIT
+76
View File
@@ -0,0 +1,76 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OPENGLCONTEXT_H
#define OPENGLCONTEXT_H
#include <QOffscreenSurface>
#include <QOpenGLFunctions>
#include <QThread>
#include "render/backend/rendercontext.h"
OLIVE_NAMESPACE_ENTER
class OpenGLContext : public RenderContext
{
Q_OBJECT
public:
OpenGLContext(QObject* parent = nullptr);
virtual ~OpenGLContext() override;
virtual bool Init() override;
public slots:
virtual void PostInit() override;
virtual void Destroy() override;
virtual QVariant CreateTexture(const VideoParams& param, void* data, int linesize) override;
virtual void DestroyTexture(QVariant texture) override;
virtual void UploadToTexture(QVariant texture, void* data, int linesize) override;
virtual void DownloadFromTexture(QVariant texture, void* data, int linesize) override;
virtual VideoParams GetParamsFromTexture(QVariant texture) override;
private:
static GLint GetInternalFormat(PixelFormat::Format format);
static GLenum GetPixelFormat(PixelFormat::Format format);
static GLenum GetPixelType(PixelFormat::Format format);
QOpenGLContext* context_;
QOpenGLFunctions* functions_;
QOffscreenSurface surface_;
QMap<GLuint, VideoParams> texture_params_;
};
OLIVE_NAMESPACE_EXIT
#endif // OPENGLCONTEXT_H
@@ -1,153 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "openglframebuffer.h"
#include <QDebug>
#include <QOpenGLExtraFunctions>
OLIVE_NAMESPACE_ENTER
OpenGLFramebuffer::OpenGLFramebuffer() :
context_(nullptr),
buffer_(0),
texture_(nullptr)
{
}
OpenGLFramebuffer::~OpenGLFramebuffer()
{
Destroy();
}
void OpenGLFramebuffer::Create(QOpenGLContext *ctx)
{
if (ctx == nullptr) {
qWarning() << "OpenGLFramebuffer::Create was passed an invalid context";
return;
}
// Free any previous framebuffer
Destroy();
context_ = ctx;
connect(context_, &QOpenGLContext::aboutToBeDestroyed, this, &OpenGLFramebuffer::Destroy);
// Create framebuffer object
context_->functions()->glGenFramebuffers(1, &buffer_);
}
void OpenGLFramebuffer::Destroy()
{
if (context_ != nullptr) {
disconnect(context_, &QOpenGLContext::aboutToBeDestroyed, this, &OpenGLFramebuffer::Destroy);
context_->functions()->glDeleteFramebuffers(1, &buffer_);
buffer_ = 0;
context_ = nullptr;
}
}
bool OpenGLFramebuffer::IsCreated() const
{
return (buffer_ > 0);
}
void OpenGLFramebuffer::Bind()
{
if (context_ == nullptr) {
return;
}
context_->functions()->glBindFramebuffer(GL_FRAMEBUFFER, buffer_);
}
void OpenGLFramebuffer::Release()
{
if (context_ == nullptr) {
return;
}
context_->functions()->glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void OpenGLFramebuffer::Attach(OpenGLTexture *texture, bool clear)
{
if (context_ == nullptr) {
return;
}
Detach();
texture_ = texture;
QOpenGLFunctions* f = context_->functions();
// bind framebuffer for attaching
f->glBindFramebuffer(GL_FRAMEBUFFER, buffer_);
context_->extraFunctions()->glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_->texture(), 0
);
if (clear) {
context_->functions()->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
context_->functions()->glClear(GL_COLOR_BUFFER_BIT);
}
// release framebuffer
f->glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void OpenGLFramebuffer::Attach(OpenGLTexturePtr texture, bool clear)
{
Attach(texture.get(), clear);
}
void OpenGLFramebuffer::Detach()
{
if (context_ == nullptr) {
return;
}
if (texture_) {
QOpenGLFunctions* f = context_->functions();
// bind framebuffer for attaching
f->glBindFramebuffer(GL_FRAMEBUFFER, buffer_);
context_->extraFunctions()->glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0
);
// release framebuffer
f->glBindFramebuffer(GL_FRAMEBUFFER, 0);
texture_ = nullptr;
}
}
const GLuint &OpenGLFramebuffer::buffer() const
{
return buffer_;
}
OLIVE_NAMESPACE_EXIT
@@ -1,65 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OPENGLFRAMEBUFFER_H
#define OPENGLFRAMEBUFFER_H
#include <QOpenGLContext>
#include "opengltexture.h"
OLIVE_NAMESPACE_ENTER
class OpenGLFramebuffer : public QObject
{
Q_OBJECT
public:
OpenGLFramebuffer();
virtual ~OpenGLFramebuffer() override;
void Create(QOpenGLContext *ctx);
bool IsCreated() const;
void Bind();
void Release();
void Attach(OpenGLTexture* texture, bool clear = false);
void Attach(OpenGLTexturePtr texture, bool clear = false);
void Detach();
const GLuint& buffer() const;
public slots:
void Destroy();
private:
QOpenGLContext* context_;
GLuint buffer_;
OpenGLTexture* texture_;
};
OLIVE_NAMESPACE_EXIT
#endif // OPENGLFRAMEBUFFER_H
-567
View File
@@ -1,567 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "openglproxy.h"
#include <QThread>
#include "common/clamp.h"
#include "core.h"
#include "node/block/transition/transition.h"
#include "node/node.h"
#include "openglcolorprocessor.h"
#include "openglrenderfunctions.h"
#include "render/colormanager.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
OpenGLProxy* OpenGLProxy::instance_ = nullptr;
OpenGLProxy::OpenGLProxy(QObject *parent) :
QObject(parent),
ctx_(nullptr),
functions_(nullptr)
{
surface_.create();
}
OpenGLProxy::~OpenGLProxy()
{
Close();
surface_.destroy();
}
void OpenGLProxy::CreateInstance()
{
instance_ = new OpenGLProxy();
QThread* proxy_thread = new QThread();
proxy_thread->start(QThread::IdlePriority);
instance_->moveToThread(proxy_thread);
if (!instance_->Init()) {
DestroyInstance();
}
}
void OpenGLProxy::DestroyInstance()
{
if (instance_) {
instance_->thread()->quit();
instance_->thread()->wait();
instance_->thread()->deleteLater();
instance_->deleteLater();
instance_ = nullptr;
}
}
bool OpenGLProxy::Init()
{
// Create context object
ctx_ = new QOpenGLContext();
// Create OpenGL context (automatically destroys any existing if there is one)
if (!ctx_->create()) {
qWarning() << "Failed to create OpenGL context in thread" << thread();
return false;
}
ctx_->moveToThread(this->thread());
// The rest of the initialization needs to occur in the other thread, so we signal for it to start
QMetaObject::invokeMethod(this, "FinishInit", Qt::QueuedConnection);
return true;
}
QVariant OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, const VideoParams& params, const RenderMode::Mode& mode)
{
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(stream);
// Set up OCIO context
QString colorspace_match = video_stream->get_colorspace_match_string();
OpenGLColorProcessorPtr color_processor = std::static_pointer_cast<OpenGLColorProcessor>(color_cache_.value(colorspace_match));
if (!color_processor) {
color_processor = OpenGLColorProcessor::Create(video_stream->footage()->project()->color_manager(),
video_stream->colorspace(),
video_stream->footage()->project()->color_manager()->GetReferenceColorSpace());
color_cache_.insert(colorspace_match, color_processor);
}
ColorManager::OCIOMethod ocio_method = ColorManager::GetOCIOMethodForMode(mode);
// OCIO's CPU conversion is more accurate, so for online we render on CPU but offline we render GPU
if (ocio_method == ColorManager::kOCIOAccurate) {
bool has_alpha = PixelFormat::FormatHasAlphaChannel(frame->format());
// Convert frame to float for OCIO
frame = PixelFormat::ConvertPixelFormat(frame,
has_alpha
? PixelFormat::PIX_FMT_RGBA32F
: PixelFormat::PIX_FMT_RGB32F);
// If alpha is associated, disassociate for the color transform
if (has_alpha && video_stream->premultiplied_alpha()) {
ColorManager::DisassociateAlpha(frame);
}
// Perform color transform
color_processor->ConvertFrame(frame);
// Associate alpha
if (has_alpha) {
if (video_stream->premultiplied_alpha()) {
ColorManager::ReassociateAlpha(frame);
} else {
ColorManager::AssociateAlpha(frame);
}
}
}
OpenGLTextureCache::ReferencePtr footage_tex_ref = texture_cache_.Get(ctx_, frame);
if (ocio_method == ColorManager::kOCIOFast) {
if (!color_processor->IsEnabled()) {
color_processor->Enable(ctx_, video_stream->premultiplied_alpha());
}
VideoParams frame_params = frame->video_params();
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(),
texture_fmt,
frame_params.pixel_aspect_ratio(),
frame_params.interlacing(),
frame_params.divider());
// Create destination texture
OpenGLTextureCache::ReferencePtr associated_tex_ref = texture_cache_.Get(ctx_, dest_params);
buffer_.Attach(associated_tex_ref->texture(), true);
buffer_.Bind();
footage_tex_ref->texture()->Bind();
// Set viewport for texture size
functions_->glViewport(0, 0, associated_tex_ref->texture()->width(), associated_tex_ref->texture()->height());
// Blit old texture to new texture through OCIO shader
color_processor->ProcessOpenGL();
footage_tex_ref->texture()->Release();
buffer_.Release();
buffer_.Detach();
footage_tex_ref = associated_tex_ref;
}
return QVariant::fromValue(footage_tex_ref);
}
QVariant OpenGLProxy::PreCachedFrameToValue(FramePtr frame)
{
return QVariant::fromValue(texture_cache_.Get(ctx_, frame));
}
OpenGLShaderPtr OpenGLProxy::ResolveShaderFromCache(const Node *node, const QString &shader_id)
{
// Make a composite of the node ID and the shader ID (if applicable)
QString full_shader_id = QStringLiteral("%1:%2").arg(node->id(), shader_id);
OpenGLShaderPtr shader = shader_cache_.value(full_shader_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(full_shader_id, shader);
} else {
qWarning() << "Failed to compile shader for" << node->id();
shader = nullptr;
}
}
return shader;
}
void OpenGLProxy::Close()
{
shader_cache_.clear();
buffer_.Destroy();
copy_pipeline_ = nullptr;
functions_ = nullptr;
delete ctx_;
ctx_ = nullptr;
}
QVariant OpenGLProxy::RunNodeAccelerated(const Node *node,
const TimeRange &range,
const ShaderJob &job,
const VideoParams& 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) {
return QVariant();
}
shader->bind();
NodeValueMap::const_iterator it;
for (it=job.GetValues().constBegin(); it!=job.GetValues().constEnd(); it++) {
// See if the shader has takes this parameter as an input
int variable_location = shader->uniformLocation(it.key());
if (variable_location == -1) {
continue;
}
// See if this value corresponds to an input (NOTE: it may not and this may be null)
NodeInput* corresponding_input = node->GetInputWithID(it.key());
// This variable is used in the shader, let's set it
const QVariant& value = it.value().data();
NodeParam::DataType data_type = (it.value().type() != NodeParam::kNone)
? it.value().type()
: corresponding_input->data_type();
switch (data_type) {
case NodeInput::kInt:
// kInt technically specifies a LongLong, but OpenGL doesn't support those. This may lead to
// over/underflows if the number is large enough, but the likelihood of that is quite low.
shader->setUniformValue(variable_location, value.toInt());
break;
case NodeInput::kFloat:
// kFloat technically specifies a double but as above, OpenGL doesn't support those.
shader->setUniformValue(variable_location, value.toFloat());
break;
case NodeInput::kVec2:
if (corresponding_input && corresponding_input->IsArray()) {
QVector<NodeValue> nv = value.value< QVector<NodeValue> >();
QVector<QVector2D> a(nv.size());
for (int j=0;j<a.size();j++) {
a[j] = nv.at(j).data().value<QVector2D>();
}
shader->setUniformValueArray(variable_location, a.constData(), a.size());
int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(it.key()));
if (count_location > -1) {
shader->setUniformValue(count_location, a.size());
}
} 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;
}
case NodeInput::kBoolean:
shader->setUniformValue(variable_location, value.toBool());
break;
case NodeInput::kBuffer:
case NodeInput::kTexture:
{
OpenGLTextureCache::ReferencePtr texture = value.value<OpenGLTextureCache::ReferencePtr>();
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 (corresponding_input && corresponding_input == 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(it.key()));
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(it.key()));
if (res_param_location > -1) {
int adjusted_width = texture->texture()->width() * texture->texture()->divider();
// Adjust virtual width by pixel aspect if necessary
if (texture->texture()->params().pixel_aspect_ratio() != 1
|| params.pixel_aspect_ratio() != 1) {
double relative_pixel_aspect = texture->texture()->params().pixel_aspect_ratio().toDouble() / params.pixel_aspect_ratio().toDouble();
adjusted_width = qRound(static_cast<double>(adjusted_width) * relative_pixel_aspect);
}
shader->setUniformValue(res_param_location,
adjusted_width,
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::kGenerateJob:
case NodeInput::kFootage:
case NodeInput::kNone:
case NodeInput::kAny:
break;
}
}
// Provide some standard args
shader->setUniformValue("ove_resolution",
static_cast<GLfloat>(params.width()),
static_cast<GLfloat>(params.height()));
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.pixel_aspect_ratio(),
params.interlacing(),
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 input_tex, output_tex;
// 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();
// 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, input_tex->texture()->texture());
OpenGLRenderFunctions::PrepareToDraw(functions_);
}
buffer_.Attach(output_tex->texture(), true);
buffer_.Bind();
// Blit this texture through this shader
OpenGLRenderFunctions::Blit(shader);
buffer_.Release();
buffer_.Detach();
}
// Release any textures we bound before
for (int i=textures_to_bind.size()-1; i>=0; i--) {
functions_->glActiveTexture(GL_TEXTURE0 + i);
functions_->glBindTexture(GL_TEXTURE_2D, 0);
}
return QVariant::fromValue(output_tex);
}
void OpenGLProxy::TextureToBuffer(const QVariant& tex_in,
FramePtr frame,
const QMatrix4x4& matrix)
{
OpenGLTextureCache::ReferencePtr texture = tex_in.value<OpenGLTextureCache::ReferencePtr>();
if (!texture) {
return;
}
OpenGLTextureCache::ReferencePtr download_tex;
if (!frame->is_allocated()) {
// If the frame isn't allocated, we'll assume that we're allocating it to the texture dimensions
frame->set_video_params(texture->texture()->params());
frame->allocate();
}
functions_->glViewport(0, 0, frame->width(), frame->height());
if (frame->width() != texture->texture()->width()
|| frame->height() != texture->texture()->height()) {
// Resize the texture if necessary
OpenGLTextureCache::ReferencePtr resized = texture_cache_.Get(ctx_, frame->video_params());
buffer_.Attach(resized->texture(), true);
buffer_.Bind();
texture->texture()->Bind();
// Blit to this new texture
OpenGLRenderFunctions::Blit(copy_pipeline_, false, matrix);
texture->texture()->Release();
buffer_.Release();
buffer_.Detach();
download_tex = resized;
} else {
download_tex = texture;
}
buffer_.Attach(download_tex->texture());
buffer_.Bind();
functions_->glPixelStorei(GL_PACK_ROW_LENGTH, frame->linesize_pixels());
functions_->glReadPixels(0,
0,
frame->width(),
frame->height(),
OpenGLRenderFunctions::GetPixelFormat(frame->format()),
OpenGLRenderFunctions::GetPixelType(frame->format()),
frame->data());
functions_->glPixelStorei(GL_PACK_ROW_LENGTH, 0);
buffer_.Release();
buffer_.Detach();
}
void OpenGLProxy::FinishInit()
{
// Make context current on that surface
if (!ctx_->makeCurrent(&surface_)) {
qWarning() << "Failed to makeCurrent() on offscreen surface in thread" << thread();
return;
}
// Store OpenGL functions instance
functions_ = ctx_->functions();
functions_->glBlendFunc(GL_ONE, GL_ZERO);
buffer_.Create(ctx_);
copy_pipeline_ = OpenGLShader::CreateDefault();
}
OLIVE_NAMESPACE_EXIT
-122
View File
@@ -1,122 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OPENGLPROXY_H
#define OPENGLPROXY_H
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include "common/timerange.h"
#include "node/value.h"
#include "openglcolorprocessor.h"
#include "openglframebuffer.h"
#include "opengltexturecache.h"
#include "render/shaderinfo.h"
OLIVE_NAMESPACE_ENTER
class OpenGLProxy : public QObject
{
Q_OBJECT
public:
OpenGLProxy(QObject* parent = nullptr);
virtual ~OpenGLProxy() override;
static void CreateInstance();
static void DestroyInstance();
static OpenGLProxy* instance()
{
return instance_;
}
/**
* @brief Initialize OpenGL instance in whatever thread this object is a part of
*
* This function creates a context (shared with share_ctx provided in the constructor) as well as various other
* OpenGL thread-specific objects necessary for rendering. This function should only ever be called from the main
* thread (i.e. the thread where share_ctx is current on) but AFTER this object has been pushed to its thread with
* moveToThread(). If this function is called from a different thread, it could fail or even segfault on some
* platforms.
*
* The reason this function must be called in the main thread (rather than initializing asynchronously in a separate
* thread) is because different platforms have different rules about creating a share context with a context that
* is still "current" in another thread. While some implementations do allow this, Windows OpenGL (wgl) explicitly
* forbids it and other platforms/drivers will segfault attempting it. While we can obviously call "doneCurrent", I
* haven't found any reliable way to prevent the main thread from making it current again before initialization is
* complete other than blocking it entirely.
*
* To get around this, we create all share contexts in the main thread and then move them to the other thread
* afterwards (which is completely legal). While annoying, this gets around the issue listed above by both preventing
* the main thread from using the context during initialization and preventing more than one shared context being made
* at the same time (which may or may not actually make a difference).
*/
bool Init();
void Close();
public slots:
QVariant RunNodeAccelerated(const OLIVE_NAMESPACE::Node *node,
const OLIVE_NAMESPACE::TimeRange &range,
const OLIVE_NAMESPACE::ShaderJob &job,
const OLIVE_NAMESPACE::VideoParams &params);
void TextureToBuffer(const QVariant& texture,
OLIVE_NAMESPACE::FramePtr frame,
const QMatrix4x4& matrix);
QVariant FrameToValue(OLIVE_NAMESPACE::FramePtr frame,
OLIVE_NAMESPACE::StreamPtr stream,
const OLIVE_NAMESPACE::VideoParams &params,
const OLIVE_NAMESPACE::RenderMode::Mode &mode);
QVariant PreCachedFrameToValue(OLIVE_NAMESPACE::FramePtr frame);
private:
OpenGLShaderPtr ResolveShaderFromCache(const Node* node, const QString &shader_id);
QOpenGLContext* ctx_;
QOffscreenSurface surface_;
QOpenGLFunctions* functions_;
OpenGLFramebuffer buffer_;
OpenGLColorProcessorCache color_cache_;
OpenGLShaderPtr copy_pipeline_;
QHash<QString, OpenGLShaderPtr> shader_cache_;
OpenGLTextureCache texture_cache_;
static OpenGLProxy* instance_;
private slots:
void FinishInit();
};
OLIVE_NAMESPACE_EXIT
#endif // OPENGLPROXY_H
@@ -1,232 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "openglrenderfunctions.h"
#include <QOpenGLExtraFunctions>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
OLIVE_NAMESPACE_ENTER
const QVector<GLfloat> blit_vertices = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f
};
const QVector<GLfloat> blit_texcoords = {
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};
const QVector<GLfloat> flipped_blit_texcoords = {
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
};
/**
* @brief Set up texture parameters and mipmap for drawing
*
* Internal function used just before drawing to allow mipmapped bilinear filtering when drawing textures small.
*
* @param f
*
* Currently active QOpenGLFunctions object (use context()->functions() if unsure).
*/
void OpenGLRenderFunctions::PrepareToDraw(QOpenGLFunctions* f)
{
f->glGenerateMipmap(GL_TEXTURE_2D);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
GLint OpenGLRenderFunctions::GetInternalFormat(const PixelFormat::Format &format)
{
switch (format) {
case PixelFormat::PIX_FMT_RGB8:
return GL_RGB8;
case PixelFormat::PIX_FMT_RGBA8:
return GL_RGBA8;
case PixelFormat::PIX_FMT_RGB16U:
return GL_RGB16;
case PixelFormat::PIX_FMT_RGBA16U:
return GL_RGBA16;
case PixelFormat::PIX_FMT_RGB16F:
return GL_RGB16F;
case PixelFormat::PIX_FMT_RGBA16F:
return GL_RGBA16F;
case PixelFormat::PIX_FMT_RGB32F:
return GL_RGB32F;
case PixelFormat::PIX_FMT_RGBA32F:
return GL_RGBA32F;
case PixelFormat::PIX_FMT_INVALID:
case PixelFormat::PIX_FMT_COUNT:
break;
}
return GL_INVALID_VALUE;
}
GLenum OpenGLRenderFunctions::GetPixelFormat(const PixelFormat::Format &format)
{
if (PixelFormat::FormatHasAlphaChannel(format)) {
return GL_RGBA;
} else {
return GL_RGB;
}
}
GLenum OpenGLRenderFunctions::GetPixelType(const PixelFormat::Format &format)
{
switch (format) {
case PixelFormat::PIX_FMT_RGB8:
case PixelFormat::PIX_FMT_RGBA8:
return GL_UNSIGNED_BYTE;
case PixelFormat::PIX_FMT_RGB16U:
case PixelFormat::PIX_FMT_RGBA16U:
return GL_UNSIGNED_SHORT;
case PixelFormat::PIX_FMT_RGB16F:
case PixelFormat::PIX_FMT_RGBA16F:
return GL_HALF_FLOAT;
case PixelFormat::PIX_FMT_RGB32F:
case PixelFormat::PIX_FMT_RGBA32F:
return GL_FLOAT;
case PixelFormat::PIX_FMT_INVALID:
case PixelFormat::PIX_FMT_COUNT:
break;
}
return GL_INVALID_VALUE;
}
void OpenGLRenderFunctions::Blit(OpenGLShaderPtr pipeline, bool flipped, QMatrix4x4 matrix)
{
Blit(pipeline.get(), flipped, matrix);
}
void OpenGLRenderFunctions::Blit(OpenGLShader *pipeline, bool flipped, QMatrix4x4 matrix)
{
Blit(pipeline,
GL_TRIANGLES,
blit_vertices,
flipped ? flipped_blit_texcoords : blit_texcoords,
matrix);
}
void OpenGLRenderFunctions::Blit(OpenGLShader *pipeline, GLenum mode, const QVector<GLfloat> &vert, const QVector<GLfloat> &tex, QMatrix4x4 matrix)
{
QOpenGLFunctions* func = QOpenGLContext::currentContext()->functions();
PrepareToDraw(func);
QOpenGLVertexArrayObject m_vao;
m_vao.create();
m_vao.bind();
QOpenGLBuffer m_vbo;
m_vbo.create();
m_vbo.bind();
m_vbo.allocate(vert.constData(), vert.size() * sizeof(GLfloat));
m_vbo.release();
QOpenGLBuffer m_vbo2;
m_vbo2.create();
m_vbo2.bind();
m_vbo2.allocate(tex.constData(), tex.size() * sizeof(GLfloat));
m_vbo2.release();
pipeline->bind();
pipeline->setUniformValue("ove_mvpmat", matrix);
pipeline->setUniformValue("ove_maintex", 0);
int vertex_location = pipeline->attributeLocation("a_position");
m_vbo.bind();
func->glEnableVertexAttribArray(vertex_location);
func->glVertexAttribPointer(vertex_location, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
m_vbo.release();
int tex_location = pipeline->attributeLocation("a_texcoord");
m_vbo2.bind();
func->glEnableVertexAttribArray(tex_location);
func->glVertexAttribPointer(tex_location, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
m_vbo2.release();
// (Size / 3) because we assume each GLfloat has an XYZ pair
func->glDrawArrays(mode, 0, blit_vertices.size() / 3);
pipeline->release();
m_vbo2.destroy();
m_vbo.destroy();
m_vao.release();
m_vao.destroy();
}
void OpenGLRenderFunctions::OCIOBlit(OpenGLShaderPtr pipeline, GLuint lut, bool flipped, QMatrix4x4 matrix)
{
OCIOBlit(pipeline.get(), lut, flipped, matrix);
}
void OpenGLRenderFunctions::OCIOBlit(OpenGLShader *pipeline,
GLuint lut,
bool flipped,
QMatrix4x4 matrix)
{
QOpenGLContext* ctx = QOpenGLContext::currentContext();
QOpenGLExtraFunctions* xf = ctx->extraFunctions();
xf->glActiveTexture(GL_TEXTURE1);
xf->glBindTexture(GL_TEXTURE_3D, lut);
xf->glActiveTexture(GL_TEXTURE0);
pipeline->bind();
pipeline->setUniformValue("ove_ociolut", 1);
Blit(pipeline, flipped, matrix);
pipeline->release();
xf->glActiveTexture(GL_TEXTURE1);
xf->glBindTexture(GL_TEXTURE_3D, 0);
xf->glActiveTexture(GL_TEXTURE0);
}
OLIVE_NAMESPACE_EXIT
@@ -1,70 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OPENGLFUNCTIONS_H
#define OPENGLFUNCTIONS_H
#include <QMatrix4x4>
#include <QMutex>
#include <QOpenGLFunctions>
#include "openglshader.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
class OpenGLRenderFunctions {
public:
/**
* @brief Draw texture on screen
*
* @param pipeline
*
* Shader to use for the texture drawing
*
* @param flipped
*
* Draw the texture vertically flipped (defaults to FALSE)
*
* @param matrix
*
* Transformation matrix to use when drawing (defaults to no transform)
*/
static void Blit(OpenGLShaderPtr pipeline, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4());
static void Blit(OpenGLShader* pipeline, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4());
static void Blit(OpenGLShader* pipeline, GLenum mode, const QVector<GLfloat>& vert,
const QVector<GLfloat>& tex, QMatrix4x4 matrix = QMatrix4x4());
static void OCIOBlit(OpenGLShaderPtr pipeline, GLuint lut, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4());
static void OCIOBlit(OpenGLShader* pipeline, GLuint lut, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4());
static void PrepareToDraw(QOpenGLFunctions* f);
static GLint GetInternalFormat(const PixelFormat::Format& format);
static GLenum GetPixelFormat(const PixelFormat::Format& format);
static GLenum GetPixelType(const PixelFormat::Format& format);
};
OLIVE_NAMESPACE_EXIT
#endif // OPENGLFUNCTIONS_H
-254
View File
@@ -1,254 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "openglshader.h"
#include <QOpenGLExtraFunctions>
OLIVE_NAMESPACE_ENTER
OpenGLShaderPtr OpenGLShader::Create()
{
return std::make_shared<OpenGLShader>();
}
OpenGLShaderPtr OpenGLShader::CreateDefault(const QString &function_name, const QString &shader_code)
{
OpenGLShaderPtr program = Create();
// Add shaders to program
program->addShaderFromSourceCode(QOpenGLShader::Vertex, CodeDefaultVertex());
program->addShaderFromSourceCode(QOpenGLShader::Fragment, CodeDefaultFragment(function_name, shader_code));
program->link();
return program;
}
// copied from source code to OCIODisplay
const int OCIO_LUT3D_EDGE_SIZE = 64;
// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE
const int OCIO_NUM_3D_ENTRIES = 3*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE;
OpenGLShaderPtr OpenGLShader::CreateOCIO(QOpenGLContext* ctx,
GLuint& lut_texture,
OCIO::ConstProcessorRcPtr processor,
bool alpha_is_associated)
{
QOpenGLExtraFunctions* xf = ctx->extraFunctions();
// Set up shader description
OCIO::GpuShaderDesc shaderDesc;
const char* ocio_func_name = "OCIODisplay";
shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3);
shaderDesc.setFunctionName(ocio_func_name);
shaderDesc.setLut3DEdgeLen(OCIO_LUT3D_EDGE_SIZE);
// Compute LUT
std::vector<float> ocio_lut_data(OCIO_NUM_3D_ENTRIES);
processor->getGpuLut3D(&ocio_lut_data[0], shaderDesc);
// Create LUT texture
xf->glGenTextures(1, &lut_texture);
// Bind LUT
xf->glActiveTexture(GL_TEXTURE1);
xf->glBindTexture(GL_TEXTURE_3D, lut_texture);
// Set texture parameters
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
// Allocate storage for texture
xf->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F,
OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE,
0, GL_RGB, GL_FLOAT, &ocio_lut_data[0]);
// Create OCIO shader code
QString shader_text;
// Workaround since OCIO doesn't support the GLSL version we use
shader_text.append(QStringLiteral("#define texture2D texture\n"
"#define texture3D texture\n"));
// Append OCIO shader code
shader_text.append(processor->getGpuShaderText(shaderDesc));
QString shader_call;
// Enforce alpha association
if (alpha_is_associated) {
// If alpha is already associated, we'll need to disassociate and reassociate
shader_text.append("\n");
QString disassociate_func_name = "disassoc";
shader_text.append(CodeAlphaDisassociate(disassociate_func_name));
QString reassociate_func_name = "reassoc";
shader_text.append(CodeAlphaReassociate(reassociate_func_name));
// Make OCIO call pass through disassociate and reassociate function
shader_call = QStringLiteral("%3(%1(%2(col), ove_ociolut));").arg(ocio_func_name,
disassociate_func_name,
reassociate_func_name);
} else {
// If alpha is not already associated, we can just associate after OCIO
// Add associate function
QString associate_func_name = "assoc";
shader_text.append(CodeAlphaAssociate(associate_func_name));
// Make OCIO call pass through associate function
shader_call = QStringLiteral("%2(%1(col, ove_ociolut));").arg(ocio_func_name, associate_func_name);
}
// Add process() function, which GetPipeline() will call if specified
QString process_function_name = "process";
shader_text.append(QStringLiteral("\n"
"uniform sampler3D ove_ociolut;\n"
"\n"
"vec4 %2(vec4 col) {\n"
" return %1\n"
"}\n").arg(shader_call, process_function_name));
// Get pipeline-based shader to inject OCIO shader into
OpenGLShaderPtr shader = OpenGLShader::CreateDefault(process_function_name, shader_text);
// Release LUT
xf->glBindTexture(GL_TEXTURE_3D, 0);
xf->glActiveTexture(GL_TEXTURE0);
return shader;
}
QString OpenGLShader::CodeDefaultFragment(QString function_name, const QString &shader_code)
{
// Create shader header
QString frag_code = QStringLiteral("#version 150\n"
"\n"
"#ifdef GL_ES\n"
"precision highp int;\n"
"precision highp float;\n"
"#endif\n"
"\n"
"uniform sampler2D ove_maintex;\n"
"uniform vec2 ove_resolution;\n"
"uniform bool ove_deinterlace;\n"
"\n"
"in vec2 ove_texcoord;\n"
"\n"
"out vec4 fragColor;\n"
"\n");
// Check if additional code was passed to this function, add it here
if (!function_name.isEmpty() && !shader_code.isEmpty()) {
// If additional code was passed, add it and reference it in main().
//
// The function in the additional code is expected to be `vec4 function_name(vec4 color)`.
// The texture coordinate can be acquired through `ove_texcoord`.
frag_code.append(shader_code);
} else {
// No function to call
function_name = QString();
}
// Our function_name arg will either resolve to the function added to this or to nothing, in
// which case they'll just be benign brackets.
frag_code.append(QStringLiteral("\n"
"void main() {\n"
" vec2 using_texcoord = ove_texcoord;\n"
" if (ove_deinterlace) {\n"
" // A very basic deinterlace that halves the vertical\n"
" // resolution and linearly interpolates the two fields\n"
" // by reading the texture coord between them.\n"
" float half_vert = round(ove_resolution.y / 2.0);\n"
" using_texcoord.y = (round(using_texcoord.y * half_vert) + 0.25) / half_vert;\n"
" }\n"
" vec4 color = %1(texture(ove_maintex, using_texcoord));\n"
" fragColor = color;\n"
"}\n").arg(function_name));
return frag_code;
}
QString OpenGLShader::CodeDefaultVertex()
{
// Generate vertex shader
return QStringLiteral("#version 150\n"
"\n"
"#ifdef GL_ES\n"
"precision highp int;\n"
"precision highp float;\n"
"#endif\n"
"\n"
"uniform mat4 ove_mvpmat;\n"
"\n"
"in vec4 a_position;\n"
"in vec2 a_texcoord;\n"
"\n"
"out vec2 ove_texcoord;\n"
"\n"
"void main() {\n"
" gl_Position = ove_mvpmat * a_position;\n"
" ove_texcoord = a_texcoord;\n"
"}\n");
}
QString OpenGLShader::CodeAlphaDisassociate(const QString &function_name)
{
return QStringLiteral("vec4 %1(vec4 col) {\n"
" if (col.a > 0.0) {\n"
" return vec4(col.rgb / col.a, col.a);"
" }\n"
" return col;\n"
"}\n").arg(function_name);
}
QString OpenGLShader::CodeAlphaReassociate(const QString &function_name)
{
return QStringLiteral("vec4 %1(vec4 col) {\n"
" if (col.a > 0.0) {\n"
" return vec4(col.rgb * col.a, col.a);"
" }\n"
" return col;\n"
"}\n").arg(function_name);
}
QString OpenGLShader::CodeAlphaAssociate(const QString &function_name)
{
return QStringLiteral("vec4 %1(vec4 col) {\n"
" return vec4(col.rgb * col.a, col.a);\n"
"}\n").arg(function_name);
}
OLIVE_NAMESPACE_EXIT
-65
View File
@@ -1,65 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OPENGLSHADER_H
#define OPENGLSHADER_H
#include <memory>
#include <QOpenGLShaderProgram>
#include <OpenColorIO/OpenColorIO.h>
namespace OCIO = OCIO_NAMESPACE::v1;
#include "common/define.h"
OLIVE_NAMESPACE_ENTER
class OpenGLShader;
using OpenGLShaderPtr = std::shared_ptr<OpenGLShader>;
/**
* @brief A simple QOpenGLShaderProgram derivative with static functions for creating
*/
class OpenGLShader : public QOpenGLShaderProgram {
public:
OpenGLShader() = default;
static OpenGLShaderPtr Create();
static OpenGLShaderPtr CreateDefault(const QString &function_name = QString(),
const QString &shader_code = QString());
static OpenGLShaderPtr CreateOCIO(QOpenGLContext* ctx,
GLuint& lut_texture,
OCIO::ConstProcessorRcPtr processor,
bool alpha_is_associated);
static QString CodeDefaultFragment(QString function_name = QString(),
const QString &shader_code = QString());
static QString CodeDefaultVertex();
static QString CodeAlphaDisassociate(const QString& function_name);
static QString CodeAlphaReassociate(const QString& function_name);
static QString CodeAlphaAssociate(const QString& function_name);
};
OLIVE_NAMESPACE_EXIT
#endif // OPENGLSHADER_H
-191
View File
@@ -1,191 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "opengltexture.h"
#include <QDateTime>
#include <QDebug>
#include <QtMath>
#include "openglrenderfunctions.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
OpenGLTexture::OpenGLTexture() :
created_ctx_(nullptr),
texture_(0)
{
}
OpenGLTexture::~OpenGLTexture()
{
Destroy();
}
bool OpenGLTexture::IsCreated() const
{
return (texture_);
}
void OpenGLTexture::Create(QOpenGLContext *ctx, const VideoParams &params, const void* data, int linesize)
{
if (!ctx) {
qWarning() << "OpenGLTexture::Create was passed an invalid context";
return;
}
Destroy();
created_ctx_ = ctx;
params_ = params;
connect(created_ctx_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy()), Qt::DirectConnection);
// Create main texture
CreateInternal(created_ctx_, &texture_, data, linesize);
}
void OpenGLTexture::Create(QOpenGLContext *ctx, const VideoParams &params)
{
Create(ctx, params, nullptr, 0);
}
void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame)
{
Create(ctx, frame.get());
}
void OpenGLTexture::Create(QOpenGLContext *ctx, Frame *frame)
{
Create(ctx, frame->video_params(), frame->data(), frame->linesize_pixels());
}
void OpenGLTexture::Destroy()
{
if (created_ctx_) {
disconnect(created_ctx_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy()));
created_ctx_->functions()->glDeleteTextures(1, &texture_);
texture_ = 0;
created_ctx_ = nullptr;
}
}
void OpenGLTexture::Bind()
{
created_ctx_->functions()->glBindTexture(GL_TEXTURE_2D, texture_);
}
void OpenGLTexture::Release()
{
created_ctx_->functions()->glBindTexture(GL_TEXTURE_2D, 0);
}
void OpenGLTexture::SetPixelAspectRatio(const rational &r)
{
params_ = VideoParams(params_.width(),
params_.height(),
params_.time_base(),
params_.format(),
r,
params_.interlacing(),
params_.divider());
}
void OpenGLTexture::Upload(FramePtr frame)
{
Upload(frame.get());
}
void OpenGLTexture::Upload(Frame *frame)
{
Upload(frame->data(), frame->linesize_pixels());
}
void OpenGLTexture::Upload(const void *data, int linesize)
{
if (!IsCreated()) {
qWarning() << "OpenGLTexture::Upload() called while it wasn't created";
return;
}
Bind();
created_ctx_->functions()->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
created_ctx_->functions()->glTexSubImage2D(GL_TEXTURE_2D,
0,
0,
0,
width(),
height(),
OpenGLRenderFunctions::GetPixelFormat(format()),
OpenGLRenderFunctions::GetPixelType(format()),
data);
created_ctx_->functions()->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
Release();
}
void OpenGLTexture::CreateInternal(QOpenGLContext* create_ctx, GLuint* tex, const void *data, int linesize)
{
QOpenGLFunctions* f = create_ctx->functions();
// Create texture
f->glGenTextures(1, tex);
// Verify texture
if (texture_ == 0) {
qWarning() << "OpenGL texture creation failed";
return;
}
// Bind texture
f->glBindTexture(GL_TEXTURE_2D, *tex);
// Set linesize
f->glPixelStorei(GL_UNPACK_ROW_LENGTH, linesize);
// Allocate storage for texture
f->glTexImage2D(GL_TEXTURE_2D,
0,
OpenGLRenderFunctions::GetInternalFormat(format()),
width(),
height(),
0,
OpenGLRenderFunctions::GetPixelFormat(format()),
OpenGLRenderFunctions::GetPixelType(format()),
data);
// Return linesize to default
f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
// Set texture filtering to bilinear
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Release texture
f->glBindTexture(GL_TEXTURE_2D, 0);
}
OLIVE_NAMESPACE_EXIT
-118
View File
@@ -1,118 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OPENGLTEXTURE_H
#define OPENGLTEXTURE_H
#include <memory>
#include <QOpenGLFunctions>
#include "codec/frame.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
/**
* @brief A class wrapper around an OpenGL texture
*/
class OpenGLTexture : public QObject
{
Q_OBJECT
public:
OpenGLTexture();
virtual ~OpenGLTexture() override;
DISABLE_COPY_MOVE(OpenGLTexture)
void Create(QOpenGLContext* ctx, const VideoParams& params, const void *data, int linesize);
void Create(QOpenGLContext* ctx, const VideoParams& params);
void Create(QOpenGLContext* ctx, FramePtr frame);
void Create(QOpenGLContext* ctx, Frame* frame);
bool IsCreated() const;
void Bind();
void Release();
const VideoParams& params() const
{
return params_;
}
const int& width() const
{
return params_.effective_width();
}
const int& height() const
{
return params_.effective_height();
}
const PixelFormat::Format &format() const
{
return params_.format();
}
const GLuint& texture() const
{
return texture_;
}
const int& divider() const
{
return params_.divider();
}
/**
* @brief Changes the pixel aspect ratio metadata of this textuer
*
* This metadata is important for our render pipeline, but we don't need to do any re-allocation
* to set it like we do with other VideoParam changes, so we provide a function to change only
* the PAR here.
*/
void SetPixelAspectRatio(const rational& r);
void Upload(FramePtr frame);
void Upload(Frame* frame);
void Upload(const void *data, int linesize);
public slots:
void Destroy();
private:
void CreateInternal(QOpenGLContext *create_ctx, GLuint *tex, const void *data, int linesize);
QOpenGLContext* created_ctx_;
GLuint texture_;
VideoParams params_;
};
using OpenGLTexturePtr = std::shared_ptr<OpenGLTexture>;
OLIVE_NAMESPACE_EXIT
Q_DECLARE_METATYPE(OLIVE_NAMESPACE::OpenGLTexturePtr)
#endif // OPENGLTEXTURE_H
@@ -1,121 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "opengltexturecache.h"
OLIVE_NAMESPACE_ENTER
OpenGLTextureCache::~OpenGLTextureCache()
{
foreach (Reference* ref, existing_references_) {
ref->ParentKilled();
}
}
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, FramePtr frame)
{
return Get(ctx, frame.get());
}
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, Frame *frame)
{
return Get(ctx, frame->video_params(), frame->data(), frame->linesize_pixels());
}
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext* ctx, const VideoParams &params, const void *data, int linesize)
{
OpenGLTexturePtr texture = nullptr;
lock_.lock();
// Iterate through textures and see if we have one that matches these parameters
for (int i=0;i<available_textures_.size();i++) {
OpenGLTexturePtr test = available_textures_.at(i);
if (test->width() == params.effective_width()
&& test->height() == params.effective_height()
&& test->format() == params.format()) {
texture = test;
available_textures_.removeAt(i);
break;
}
}
// If we didn't find a texture, we'll need to create one
if (!texture) {
texture = std::make_shared<OpenGLTexture>();
texture->Create(ctx, params);
}
texture->SetPixelAspectRatio(params.pixel_aspect_ratio());
ReferencePtr ref = std::make_shared<Reference>(this, texture);
existing_references_.append(ref.get());
lock_.unlock();
if (data) {
texture->Upload(data, linesize);
}
return ref;
}
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoParams &params)
{
return Get(ctx, params, nullptr, 0);
}
void OpenGLTextureCache::Relinquish(OpenGLTextureCache::Reference *ref)
{
OpenGLTexturePtr tex = ref->texture();
lock_.lock();
existing_references_.removeOne(ref);
available_textures_.append(tex);
lock_.unlock();
}
OpenGLTextureCache::Reference::Reference(OpenGLTextureCache *parent, OpenGLTexturePtr texture) :
parent_(parent),
texture_(texture)
{
}
OpenGLTextureCache::Reference::~Reference()
{
if (parent_) {
parent_->Relinquish(this);
}
}
OpenGLTexturePtr OpenGLTextureCache::Reference::texture()
{
return texture_;
}
void OpenGLTextureCache::Reference::ParentKilled()
{
parent_ = nullptr;
}
OLIVE_NAMESPACE_EXIT
@@ -1,80 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OPENGLTEXTURECACHE_H
#define OPENGLTEXTURECACHE_H
#include <QMutex>
#include "openglframebuffer.h"
#include "opengltexture.h"
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
class OpenGLTextureCache
{
public:
class Reference {
public:
Reference(OpenGLTextureCache* parent, OpenGLTexturePtr texture);
~Reference();
DISABLE_COPY_MOVE(Reference)
OpenGLTexturePtr texture();
void ParentKilled();
private:
OpenGLTextureCache* parent_;
OpenGLTexturePtr texture_;
};
using ReferencePtr = std::shared_ptr<Reference>;
OpenGLTextureCache() = default;
~OpenGLTextureCache();
DISABLE_COPY_MOVE(OpenGLTextureCache)
ReferencePtr Get(QOpenGLContext *ctx, FramePtr frame);
ReferencePtr Get(QOpenGLContext *ctx, Frame* frame);
ReferencePtr Get(QOpenGLContext *ctx, const VideoParams& params, const void *data, int linesize);
ReferencePtr Get(QOpenGLContext *ctx, const VideoParams& params);
private:
void Relinquish(Reference* ref);
QMutex lock_;
QList<OpenGLTexturePtr> available_textures_;
QList<Reference*> existing_references_;
};
OLIVE_NAMESPACE_EXIT
Q_DECLARE_METATYPE(OLIVE_NAMESPACE::OpenGLTextureCache::ReferencePtr)
#endif // OPENGLTEXTURECACHE_H
+31 -1
View File
@@ -1,6 +1,36 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "rendercontext.h"
RenderContext::RenderContext()
OLIVE_NAMESPACE_ENTER
RenderContext::RenderContext(QObject *parent) :
QObject(parent)
{
}
QVariant RenderContext::CreateTexture(const VideoParams &param)
{
return CreateTexture(param, nullptr, 0);
}
OLIVE_NAMESPACE_EXIT
+56 -2
View File
@@ -1,11 +1,65 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef RENDERCONTEXT_H
#define RENDERCONTEXT_H
#include <QObject>
#include <QVariant>
class RenderContext
#include "common/define.h"
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
class RenderContext : public QObject
{
Q_OBJECT
public:
RenderContext();
RenderContext(QObject* parent = nullptr);
virtual ~RenderContext() override;
virtual bool Init() = 0;
public slots:
virtual void PostInit() = 0;
virtual void Destroy() = 0;
virtual QVariant CreateTexture(const OLIVE_NAMESPACE::VideoParams& param, void* data, int linesize) = 0;
virtual void DestroyTexture(QVariant texture) = 0;
virtual void UploadToTexture(QVariant texture, void* data, int linesize) = 0;
virtual void DownloadFromTexture(QVariant texture, void* data, int linesize) = 0;
virtual QVariant CreateShader();
virtual VideoParams GetParamsFromTexture(QVariant texture) = 0;
QVariant CreateTexture(const OLIVE_NAMESPACE::VideoParams& param);
};
OLIVE_NAMESPACE_EXIT
#endif // RENDERCONTEXT_H
@@ -0,0 +1,107 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "rendercontextthreadwrapper.h"
OLIVE_NAMESPACE_ENTER
RenderContextThreadWrapper::RenderContextThreadWrapper(RenderContext *inner, QObject *parent) :
RenderContext(parent),
inner_(inner),
thread_(nullptr)
{
inner_->setParent(this);
}
bool RenderContextThreadWrapper::Init()
{
// Create thread
QThread* thread = new QThread(this);
thread->start(QThread::IdlePriority);
// Move context to thread
inner_->moveToThread(thread);
// Init context in main thread
inner_->Init();
// Queue post-init in new thread
QMetaObject::invokeMethod(inner_, "PostInit", Qt::QueuedConnection);
}
void RenderContextThreadWrapper::Destroy()
{
if (thread_) {
QMetaObject::invokeMethod(inner_, "Destroy", Qt::QueuedConnection);
thread_->quit();
thread_->wait();
delete thread_;
thread_ = nullptr;
}
}
QVariant RenderContextThreadWrapper::CreateTexture(const VideoParams &param, void *data, int linesize)
{
QVariant v;
QMetaObject::invokeMethod(inner_, "CreateTexture", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVariant, v),
OLIVE_NS_CONST_ARG(VideoParams&, param),
Q_ARG(void*, data),
Q_ARG(int, linesize));
return v;
}
void RenderContextThreadWrapper::DestroyTexture(QVariant texture)
{
QMetaObject::invokeMethod(inner_, "DestroyTexture", Qt::QueuedConnection,
Q_ARG(QVariant, texture));
}
void RenderContextThreadWrapper::UploadToTexture(QVariant texture, void *data, int linesize)
{
QMetaObject::invokeMethod(inner_, "UploadToTexture", Qt::QueuedConnection,
Q_ARG(QVariant, texture),
Q_ARG(void*, data),
Q_ARG(int, linesize));
}
void RenderContextThreadWrapper::DownloadFromTexture(QVariant texture, void *data, int linesize)
{
QMetaObject::invokeMethod(inner_, "DownloadFromTexture", Qt::QueuedConnection,
Q_ARG(QVariant, texture),
Q_ARG(void*, data),
Q_ARG(int, linesize));
}
VideoParams RenderContextThreadWrapper::GetParamsFromTexture(QVariant texture)
{
VideoParams p;
QMetaObject::invokeMethod(inner_, "GetParamsFromTexture", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(VideoParams, p),
Q_ARG(QVariant, texture));
return p;
}
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,66 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef RENDERCONTEXTTHREADWRAPPER_H
#define RENDERCONTEXTTHREADWRAPPER_H
#include <QThread>
#include "rendercontext.h"
OLIVE_NAMESPACE_ENTER
class RenderContextThreadWrapper : public RenderContext
{
public:
RenderContextThreadWrapper(RenderContext* inner, QObject* parent = nullptr);
virtual ~RenderContextThreadWrapper() override
{
Destroy();
}
virtual bool Init() override;
public slots:
virtual void PostInit() override{}
virtual void Destroy() override;
virtual QVariant CreateTexture(const VideoParams& param, void* data, int linesize) override;
virtual void DestroyTexture(QVariant texture) override;
virtual void UploadToTexture(QVariant texture, void* data, int linesize) override;
virtual void DownloadFromTexture(QVariant texture, void* data, int linesize) override;
virtual VideoParams GetParamsFromTexture(QVariant texture) override;
private:
RenderContext* inner_;
QThread* thread_;
};
OLIVE_NAMESPACE_EXIT
#endif // RENDERCONTEXTTHREADWRAPPER_H
-6
View File
@@ -1,6 +0,0 @@
#include "renderframebuffer.h"
RenderFrameBuffer::RenderFrameBuffer()
{
}
-11
View File
@@ -1,11 +0,0 @@
#ifndef RENDERFRAMEBUFFER_H
#define RENDERFRAMEBUFFER_H
class RenderFrameBuffer
{
public:
RenderFrameBuffer();
};
#endif // RENDERFRAMEBUFFER_H
-6
View File
@@ -1,6 +0,0 @@
#include "rendershader.h"
RenderShader::RenderShader()
{
}
-11
View File
@@ -1,11 +0,0 @@
#ifndef RENDERSHADER_H
#define RENDERSHADER_H
class RenderShader
{
public:
RenderShader();
};
#endif // RENDERSHADER_H
-6
View File
@@ -1,6 +0,0 @@
#include "rendertexture.h"
RenderTexture::RenderTexture(RenderContext *ctx)
{
}
-12
View File
@@ -1,12 +0,0 @@
#ifndef RENDERTEXTURE_H
#define RENDERTEXTURE_H
#include "rendercontext.h"
class RenderTexture
{
public:
RenderTexture(RenderContext* ctx);
};
#endif // RENDERTEXTURE_H
+30
View File
@@ -4,6 +4,7 @@
#include <QtConcurrent/QtConcurrent>
#include "render/rendermanager.h"
#include "render/renderprocessor.h"
OLIVE_NAMESPACE_ENTER
@@ -177,6 +178,35 @@ void PreviewAutoCacher::AudioRendered()
viewer_node_->audio_playback_cache()->WritePCM(audio_tasks_.value(watcher),
watcher->Get().value<SampleBufferPtr>(),
watcher->GetTicket()->GetJobTime());
// Retrieve visual waveforms
QVector<RenderProcessor::RenderedWaveform> waveform_list = watcher->GetTicket()->property("waveforms").value< QVector<RenderProcessor::RenderedWaveform> >();
foreach (const RenderProcessor::RenderedWaveform& waveform_info, waveform_list) {
// Find original track
TrackOutput* track = nullptr;
for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) {
if (it.value() == waveform_info.track) {
track = static_cast<TrackOutput*>(it.key());
break;
}
}
if (track) {
QList<TimeRange> valid_ranges = viewer_node_->audio_playback_cache()->GetValidRanges(waveform_info.range,
watcher->GetTicket()->GetJobTime());
if (!valid_ranges.isEmpty()) {
// Generate visual waveform in this background thread
track->waveform().set_channel_count(viewer_node_->audio_params().channel_count());
foreach (const TimeRange& r, valid_ranges) {
track->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length());
}
emit track->PreviewChanged();
}
}
}
}
audio_tasks_.remove(watcher);
+20 -88
View File
@@ -22,11 +22,14 @@
#include <QApplication>
#include <QDateTime>
#include <QMatrix4x4>
#include <QThread>
#include "config/config.h"
#include "core.h"
#include "render/backend/opengl/openglproxy.h"
#include "render/backend/opengl/openglcontext.h"
#include "render/backend/rendercontextthreadwrapper.h"
#include "renderprocessor.h"
#include "task/conform/conform.h"
#include "task/taskmanager.h"
#include "window/mainwindow/mainwindow.h"
@@ -38,13 +41,8 @@ RenderManager* RenderManager::instance_ = nullptr;
RenderManager::RenderManager(QObject *parent) :
ThreadPool(QThread::IdlePriority, 0, parent)
{
// Initialize OpenGL service
OpenGLProxy::CreateInstance();
}
RenderManager::~RenderManager()
{
OpenGLProxy::DestroyInstance();
context_ = new RenderContextThreadWrapper(new OpenGLContext(), this);
context_->Init();
}
QByteArray RenderManager::Hash(const Node *n, const VideoParams &params, const rational &time)
@@ -63,13 +61,23 @@ QByteArray RenderManager::Hash(const Node *n, const VideoParams &params, const r
return hasher.result();
}
RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, const rational &time, RenderMode::Mode mode, bool prioritize)
RenderTicketPtr RenderManager::RenderFrame(ViewerOutput *viewer, const rational &time, RenderMode::Mode mode, bool prioritize)
{
return RenderFrame(viewer, time, mode,
QSize(),
QMatrix4x4(),
prioritize);
}
RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, const rational &time, RenderMode::Mode mode, const QSize &force_size, const QMatrix4x4 &matrix, bool prioritize)
{
// Create ticket
RenderTicketPtr ticket = std::make_shared<RenderTicket>();
ticket->setProperty("viewer", Node::PtrToValue(viewer));
ticket->setProperty("time", QVariant::fromValue(time));
ticket->setProperty("size", force_size);
ticket->setProperty("matrix", matrix);
ticket->setProperty("mode", mode);
ticket->setProperty("type", kTypeVideo);
@@ -81,7 +89,7 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, const rational
return ticket;
}
RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange &r, bool prioritize)
RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange &r, bool generate_waveforms, bool prioritize)
{
// Create ticket
RenderTicketPtr ticket = std::make_shared<RenderTicket>();
@@ -89,6 +97,7 @@ RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange
ticket->setProperty("viewer", Node::PtrToValue(viewer));
ticket->setProperty("time", QVariant::fromValue(r));
ticket->setProperty("type", kTypeAudio);
ticket->setProperty("waveforms", generate_waveforms);
// Queue appending the ticket and running the next job on our thread to make this function thread-safe
QMetaObject::invokeMethod(this, "AddTicket", Qt::AutoConnection,
@@ -118,84 +127,7 @@ RenderTicketPtr RenderManager::SaveFrameToCache(FrameHashCache *cache, FramePtr
void RenderManager::RunTicket(RenderTicketPtr ticket) const
{
// Depending on the render ticket type, start a job
TicketType type = ticket->property("type").value<TicketType>();
switch (type) {
case kTypeVideo:
RenderFrameInternal(ticket);
break;
case kTypeAudio:
RenderAudioInternal(ticket);
break;
case kTypeVideoDownload:
SaveFrameToCacheInternal(ticket);
break;
default:
// Fail
ticket->Cancel();
}
}
void RenderManager::RenderFrameInternal(RenderTicketPtr ticket)
{
ViewerOutput* viewer = Node::ValueToPtr<ViewerOutput>(ticket->property("viewer"));
rational time = ticket->property("time").value<rational>();
ticket->Start();
qDebug() << "STUB: Rendered" << time << "frames for" << viewer;
FramePtr frame = Frame::Create();
frame->set_video_params(viewer->video_params());
frame->allocate();
ticket->Finish(QVariant::fromValue(frame), false);
}
void RenderManager::RenderAudioInternal(RenderTicketPtr ticket)
{
ViewerOutput* viewer = Node::ValueToPtr<ViewerOutput>(ticket->property("viewer"));
TimeRange time = ticket->property("time").value<TimeRange>();
ticket->Start();
qDebug() << "STUB: Rendered" << time << "audio for" << viewer;
ticket->Finish(QVariant::fromValue(SampleBuffer::CreateAllocated(viewer->audio_params(), time.length())), false);
}
void RenderManager::SaveFrameToCacheInternal(RenderTicketPtr ticket)
{
FrameHashCache* cache = Node::ValueToPtr<FrameHashCache>(ticket->property("cache"));
FramePtr frame = ticket->property("frame").value<FramePtr>();
QByteArray hash = ticket->property("hash").toByteArray();
ticket->Start();
ticket->Finish(cache->SaveCacheFrame(hash, frame), false);
}
void RenderManager::WorkerGeneratedWaveform(RenderTicketPtr ticket, TrackOutput *track, AudioVisualWaveform samples, TimeRange range)
{
ViewerOutput* viewer = Node::ValueToPtr<ViewerOutput>(ticket->property("viewer"));
QList<TimeRange> valid_ranges = viewer->audio_playback_cache()->GetValidRanges(range,
ticket->GetJobTime());
if (!valid_ranges.isEmpty()) {
// Generate visual waveform in this background thread
track->waveform_lock()->lock();
track->waveform().set_channel_count(viewer->audio_params().channel_count());
foreach (const TimeRange& r, valid_ranges) {
track->waveform().OverwriteSums(samples, r.in(), r.in() - range.in(), r.length());
}
track->waveform_lock()->unlock();
emit track->PreviewChanged();
}
RenderProcessor::Process(ticket, context_);
}
OLIVE_NAMESPACE_EXIT
+5 -11
View File
@@ -29,6 +29,8 @@
#include "decodercache.h"
#include "node/graph.h"
#include "node/output/viewer/viewer.h"
#include "node/traverser.h"
#include "render/backend/rendercontext.h"
#include "threading/threadpool.h"
OLIVE_NAMESPACE_ENTER
@@ -78,6 +80,7 @@ public:
* This function is thread-safe.
*/
RenderTicketPtr RenderFrame(ViewerOutput* viewer, const rational& time, RenderMode::Mode mode, bool prioritize = false);
RenderTicketPtr RenderFrame(ViewerOutput* viewer, const rational& time, RenderMode::Mode mode, const QSize& force_size, const QMatrix4x4& matrix, bool prioritize = false);
/**
* @brief Asynchronously generate a chunk of audio
@@ -89,7 +92,7 @@ public:
*
* This function is thread-safe.
*/
RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, bool prioritize = false);
RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, bool generate_waveforms, bool prioritize = false);
RenderTicketPtr SaveFrameToCache(FrameHashCache* cache, FramePtr frame, const QByteArray& hash, bool prioritize = false);
@@ -104,20 +107,11 @@ public:
signals:
private:
static void RenderFrameInternal(RenderTicketPtr ticket);
static void RenderAudioInternal(RenderTicketPtr ticket);
static void SaveFrameToCacheInternal(RenderTicketPtr ticket);
RenderManager(QObject* parent = nullptr);
virtual ~RenderManager() override;
static RenderManager* instance_;
private slots:
void WorkerGeneratedWaveform(OLIVE_NAMESPACE::RenderTicketPtr ticket, OLIVE_NAMESPACE::TrackOutput* track, OLIVE_NAMESPACE::AudioVisualWaveform samples, OLIVE_NAMESPACE::TimeRange range);
RenderContext* context_;
};
+630
View File
@@ -0,0 +1,630 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "renderprocessor.h"
#include "rendermanager.h"
OLIVE_NAMESPACE_ENTER
RenderProcessor::RenderProcessor(RenderTicketPtr ticket, RenderContext *render_ctx) :
ticket_(ticket),
render_ctx_(render_ctx)
{
}
void RenderProcessor::Run()
{
// Depending on the render ticket type, start a job
RenderManager::TicketType type = ticket_->property("type").value<RenderManager::TicketType>();
ticket_->Start();
switch (type) {
case RenderManager::kTypeVideo:
{
ViewerOutput* viewer = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"));
rational time = ticket_->property("time").value<rational>();
NodeValueTable table = ProcessInput(viewer->texture_input(),
TimeRange(time, time + viewer->video_params().time_base()));
QVariant texture = table.Get(NodeParam::kTexture);
QSize frame_size = ticket_->property("size").value<QSize>();
if (frame_size.isNull()) {
frame_size = QSize(viewer->video_params().effective_width(),
viewer->video_params().effective_height());
}
FramePtr frame = Frame::Create();
frame->set_timestamp(time);
frame->set_video_params(VideoParams(frame_size.width(),
frame_size.height(),
viewer->video_params().time_base(),
viewer->video_params().format(),
viewer->video_params().pixel_aspect_ratio(),
viewer->video_params().interlacing(),
viewer->video_params().divider()));
frame->allocate();
if (texture.isNull()) {
// Blank frame out
memset(frame->data(), 0, frame->allocated_size());
} else {
// Dump texture contents to frame
VideoParams tex_params = render_ctx_->GetParamsFromTexture(texture);
if (tex_params.width() != frame->width() || tex_params.height() != frame->height()) {
// FIXME: Blit this shit
}
render_ctx_->DownloadFromTexture(texture, frame->data(), frame->linesize_pixels());
}
ticket_->Finish(QVariant::fromValue(frame), IsCancelled());
break;
}
case RenderManager::kTypeAudio:
{
ViewerOutput* viewer = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"));
TimeRange time = ticket_->property("time").value<TimeRange>();
NodeValueTable table = ProcessInput(viewer->samples_input(), time);
ticket_->Finish(table.Get(NodeParam::kSamples), IsCancelled());
break;
}
case RenderManager::kTypeVideoDownload:
{
FrameHashCache* cache = Node::ValueToPtr<FrameHashCache>(ticket_->property("cache"));
FramePtr frame = ticket_->property("frame").value<FramePtr>();
QByteArray hash = ticket_->property("hash").toByteArray();
ticket_->Finish(cache->SaveCacheFrame(hash, frame), false);
break;
}
default:
// Fail
ticket_->Cancel();
}
this->deleteLater();
}
void RenderProcessor::Process(RenderTicketPtr ticket, RenderContext *render_ctx)
{
RenderProcessor p(ticket, render_ctx);
p.Run();
}
NodeValueTable RenderProcessor::GenerateBlockTable(const TrackOutput *track, const TimeRange &range)
{
if (track->track_type() == Timeline::kTrackTypeAudio) {
const AudioParams& audio_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->audio_params();
QList<Block*> active_blocks = track->BlocksAtTimeRange(range);
// All these blocks will need to output to a buffer so we create one here
SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params,
audio_params.time_to_samples(range.length()));
block_range_buffer->fill(0);
NodeValueTable merged_table;
// Loop through active blocks retrieving their audio
foreach (Block* b, active_blocks) {
TimeRange range_for_block(qMax(b->in(), range.in()),
qMin(b->out(), range.out()));
int destination_offset = audio_params.time_to_samples(range_for_block.in() - range.in());
int max_dest_sz = audio_params.time_to_samples(range_for_block.length());
// Destination buffer
NodeValueTable table = GenerateTable(b, range_for_block);
SampleBufferPtr samples_from_this_block = table.Take(NodeParam::kSamples).value<SampleBufferPtr>();
if (!samples_from_this_block) {
// If we retrieved no samples from this block, do nothing
continue;
}
// FIXME: Doesn't handle reversing
if (b->speed_input()->is_keyframing() || b->speed_input()->is_connected()) {
// FIXME: We'll need to calculate the speed hoo boy
} else {
double speed_value = b->speed_input()->get_standard_value().toDouble();
if (qIsNull(speed_value)) {
// Just silence, don't think there's any other practical application of 0 speed audio
samples_from_this_block->fill(0);
} else if (!qFuzzyCompare(speed_value, 1.0)) {
// Multiply time
samples_from_this_block->speed(speed_value);
}
}
int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count());
// Copy samples into destination buffer
block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length);
NodeValueTable::Merge({merged_table, table});
}
if (ticket_->property("waveforms").toBool()) {
// Generate a visual waveform and send it back to the main thread
AudioVisualWaveform visual_waveform;
visual_waveform.set_channel_count(audio_params.channel_count());
visual_waveform.OverwriteSamples(block_range_buffer, audio_params.sample_rate());
RenderedWaveform waveform_info = {track, visual_waveform, range};
QVector<RenderedWaveform> waveform_list = ticket_->property("waveforms").value< QVector<RenderedWaveform> >();
waveform_list.append(waveform_info);
ticket_->setProperty("waveforms", QVariant::fromValue(waveform_list));
}
merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer), track);
return merged_table;
} else {
return NodeTraverser::GenerateBlockTable(track, range);
}
}
QVariant RenderProcessor::ProcessVideoFootage(StreamPtr stream, const rational &input_time)
{
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(stream);
rational time_match = (video_stream->video_type() == VideoStream::kVideoTypeStill) ? 0 : input_time;
QString colorspace_match = video_stream->get_colorspace_match_string();
QVariant value;
bool found_cache = false;
const VideoParams& video_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->video_params();
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 (!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()) {
// 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});
}
}
}
}
return value;
}
QVariant RenderProcessor::ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time)
{
QVariant value;
DecoderPtr decoder = ResolveDecoderFromInput(stream);
if (decoder) {
const AudioParams& audio_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->audio_params();
// See if we have a conformed version of this audio
if (!decoder->HasConformedVersion(audio_params)) {
// If not, the audio needs to be conformed
// 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) {
value = QVariant::fromValue(frame);
}
}
}
return value;
}
QVariant RenderProcessor::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job)
{
// 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) {
return QVariant();
}
shader->bind();
NodeValueMap::const_iterator it;
for (it=job.GetValues().constBegin(); it!=job.GetValues().constEnd(); it++) {
// See if the shader has takes this parameter as an input
int variable_location = shader->uniformLocation(it.key());
if (variable_location == -1) {
continue;
}
// See if this value corresponds to an input (NOTE: it may not and this may be null)
NodeInput* corresponding_input = node->GetInputWithID(it.key());
// This variable is used in the shader, let's set it
const QVariant& value = it.value().data();
NodeParam::DataType data_type = (it.value().type() != NodeParam::kNone)
? it.value().type()
: corresponding_input->data_type();
switch (data_type) {
case NodeInput::kInt:
// kInt technically specifies a LongLong, but OpenGL doesn't support those. This may lead to
// over/underflows if the number is large enough, but the likelihood of that is quite low.
shader->setUniformValue(variable_location, value.toInt());
break;
case NodeInput::kFloat:
// kFloat technically specifies a double but as above, OpenGL doesn't support those.
shader->setUniformValue(variable_location, value.toFloat());
break;
case NodeInput::kVec2:
if (corresponding_input && corresponding_input->IsArray()) {
QVector<NodeValue> nv = value.value< QVector<NodeValue> >();
QVector<QVector2D> a(nv.size());
for (int j=0;j<a.size();j++) {
a[j] = nv.at(j).data().value<QVector2D>();
}
shader->setUniformValueArray(variable_location, a.constData(), a.size());
int count_location = shader->uniformLocation(QStringLiteral("%1_count").arg(it.key()));
if (count_location > -1) {
shader->setUniformValue(count_location, a.size());
}
} 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;
}
case NodeInput::kBoolean:
shader->setUniformValue(variable_location, value.toBool());
break;
case NodeInput::kBuffer:
case NodeInput::kTexture:
{
OpenGLTextureCache::ReferencePtr texture = value.value<OpenGLTextureCache::ReferencePtr>();
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 (corresponding_input && corresponding_input == 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(it.key()));
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(it.key()));
if (res_param_location > -1) {
int adjusted_width = texture->texture()->width() * texture->texture()->divider();
// Adjust virtual width by pixel aspect if necessary
if (texture->texture()->params().pixel_aspect_ratio() != 1
|| params.pixel_aspect_ratio() != 1) {
double relative_pixel_aspect = texture->texture()->params().pixel_aspect_ratio().toDouble() / params.pixel_aspect_ratio().toDouble();
adjusted_width = qRound(static_cast<double>(adjusted_width) * relative_pixel_aspect);
}
shader->setUniformValue(res_param_location,
adjusted_width,
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::kGenerateJob:
case NodeInput::kFootage:
case NodeInput::kNone:
case NodeInput::kAny:
break;
}
}
// Provide some standard args
shader->setUniformValue("ove_resolution",
static_cast<GLfloat>(params.width()),
static_cast<GLfloat>(params.height()));
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.pixel_aspect_ratio(),
params.interlacing(),
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 input_tex, output_tex;
// 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();
// 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, input_tex->texture()->texture());
OpenGLRenderFunctions::PrepareToDraw(functions_);
}
buffer_.Attach(output_tex->texture(), true);
buffer_.Bind();
// Blit this texture through this shader
OpenGLRenderFunctions::Blit(shader);
buffer_.Release();
buffer_.Detach();
}
// Release any textures we bound before
for (int i=textures_to_bind.size()-1; i>=0; i--) {
functions_->glActiveTexture(GL_TEXTURE0 + i);
functions_->glBindTexture(GL_TEXTURE_2D, 0);
}
return QVariant::fromValue(output_tex);
}
QVariant RenderProcessor::ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job)
{
if (!job.samples() || !job.samples()->is_allocated()) {
return QVariant();
}
SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(job.samples()->audio_params(), job.samples()->sample_count());
NodeValueDatabase value_db;
const AudioParams& audio_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->audio_params();
for (int i=0;i<job.samples()->sample_count();i++) {
// Calculate the exact rational time at this sample
double sample_to_second = static_cast<double>(i) / static_cast<double>(audio_params.sample_rate());
rational this_sample_time = rational::fromDouble(range.in().toDouble() + sample_to_second);
// Update all non-sample and non-footage inputs
NodeValueMap::const_iterator j;
for (j=job.GetValues().constBegin(); j!=job.GetValues().constEnd(); j++) {
NodeValueTable value;
NodeInput* corresponding_input = node->GetInputWithID(j.key());
if (corresponding_input) {
value = ProcessInput(corresponding_input, TimeRange(this_sample_time, this_sample_time));
} else {
value.Push(j.value());
}
value_db.Insert(j.key(), value);
}
AddGlobalsToDatabase(value_db, TimeRange(this_sample_time, this_sample_time));
node->ProcessSamples(value_db,
job.samples(),
output_buffer,
i);
}
return QVariant::fromValue(output_buffer);
}
QVariant RenderProcessor::ProcessFrameGeneration(const Node *node, const GenerateJob &job)
{
FramePtr frame = Frame::Create();
const VideoParams& video_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->video_params();
PixelFormat::Format output_fmt;
if (job.GetAlphaChannelRequired()) {
output_fmt = PixelFormat::GetFormatWithAlphaChannel(video_params.format());
} else {
output_fmt = PixelFormat::GetFormatWithoutAlphaChannel(video_params.format());
}
frame->set_video_params(VideoParams(video_params.width(),
video_params.height(),
video_params.time_base(),
output_fmt,
video_params.pixel_aspect_ratio(),
video_params.interlacing(),
video_params.divider()));
frame->allocate();
node->GenerateFrame(frame, job);
return CachedFrameToTexture(frame);
}
QVariant RenderProcessor::GetCachedFrame(const Node *node, const rational &time)
{
if (ticket_->property("mode").value<RenderMode::Mode>() == RenderMode::kOffline
&& !cache_path_.isEmpty()
&& node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) {
const VideoParams& video_params = Node::ValueToPtr<ViewerOutput>(ticket_->property("viewer"))->video_params();
QByteArray hash = RenderManager::Hash(node, video_params, time);
FramePtr f = FrameHashCache::LoadCacheFrame(cache_path_, hash);
if (f) {
// The cached frame won't load with the correct divider by default, so we enforce it here
f->set_video_params(VideoParams(f->width() * video_params.divider(),
f->height() * video_params.divider(),
f->video_params().time_base(),
f->video_params().format(),
f->video_params().pixel_aspect_ratio(),
f->video_params().interlacing(),
video_params.divider()));
return CachedFrameToTexture(f);
}
}
return QVariant();
}
OLIVE_NAMESPACE_EXIT
+75
View File
@@ -0,0 +1,75 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef RENDERPROCESSOR_H
#define RENDERPROCESSOR_H
#include "node/traverser.h"
#include "render/backend/rendercontext.h"
#include "threading/threadticket.h"
OLIVE_NAMESPACE_ENTER
class RenderProcessor : public NodeTraverser, public QObject
{
Q_OBJECT
public:
static void Process(RenderTicketPtr ticket, RenderContext* render_ctx);
struct RenderedWaveform {
const TrackOutput* track;
AudioVisualWaveform waveform;
TimeRange range;
};
signals:
void GeneratedFrame(FramePtr frame);
void GeneratedAudio(SampleBufferPtr audio);
protected:
virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange &range) override;
virtual QVariant ProcessVideoFootage(StreamPtr stream, const rational &input_time) override;
virtual QVariant ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) override;
virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override;
virtual QVariant ProcessSamples(const Node *node, const TimeRange &range, const SampleJob &job) override;
virtual QVariant ProcessFrameGeneration(const Node *node, const GenerateJob& job) override;
virtual QVariant GetCachedFrame(const Node *node, const rational &time) override;
private:
RenderProcessor(RenderTicketPtr ticket, RenderContext* render_ctx);
void Run();
RenderTicketPtr ticket_;
RenderContext* render_ctx_;
};
OLIVE_NAMESPACE_EXIT
#endif // RENDERPROCESSOR_H
-2
View File
@@ -69,8 +69,6 @@ public:
void RunTicket(RenderTicketPtr ticket);
void Cancel();
protected:
virtual void run() override;