diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 6e32f1c9c..b498f4b68 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -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); diff --git a/app/render/CMakeLists.txt b/app/render/CMakeLists.txt index 44b590ee6..1b3c2b9fc 100644 --- a/app/render/CMakeLists.txt +++ b/app/render/CMakeLists.txt @@ -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 diff --git a/app/render/backend/CMakeLists.txt b/app/render/backend/CMakeLists.txt index 8dc59590c..65c369e52 100644 --- a/app/render/backend/CMakeLists.txt +++ b/app/render/backend/CMakeLists.txt @@ -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 ) diff --git a/app/render/backend/opengl/CMakeLists.txt b/app/render/backend/opengl/CMakeLists.txt index cb8f1b674..9464d26bd 100644 --- a/app/render/backend/opengl/CMakeLists.txt +++ b/app/render/backend/opengl/CMakeLists.txt @@ -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 ) diff --git a/app/render/backend/opengl/openglcolorprocessor.cpp b/app/render/backend/opengl/openglcolorprocessor.cpp deleted file mode 100644 index bc0993f4f..000000000 --- a/app/render/backend/opengl/openglcolorprocessor.cpp +++ /dev/null @@ -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 . - -***/ - -#include "openglcolorprocessor.h" - -#include -#include - -#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(config, source_space, dest_space); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/opengl/openglcolorprocessor.h b/app/render/backend/opengl/openglcolorprocessor.h deleted file mode 100644 index bd8e6dc9c..000000000 --- a/app/render/backend/opengl/openglcolorprocessor.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OPENGLCOLORPROCESSOR_H -#define OPENGLCOLORPROCESSOR_H - -#include "openglshader.h" -#include "render/colorprocessor.h" - -OLIVE_NAMESPACE_ENTER - -class OpenGLColorProcessor; -using OpenGLColorProcessorPtr = std::shared_ptr; - -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; - -OLIVE_NAMESPACE_EXIT - -#endif // OPENGLCOLORPROCESSOR_H diff --git a/app/render/backend/opengl/openglcontext.cpp b/app/render/backend/opengl/openglcontext.cpp new file mode 100644 index 000000000..5a81f4719 --- /dev/null +++ b/app/render/backend/opengl/openglcontext.cpp @@ -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 . + +***/ + +#include "openglcontext.h" + +#include + +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(); + functions_->glDeleteTextures(1, &t); + texture_params_.remove(t); +} + +void OpenGLContext::UploadToTexture(QVariant texture, void *data, int linesize) +{ + GLuint t = texture.value(); + 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(); + 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(); + + 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 diff --git a/app/render/backend/opengl/openglcontext.h b/app/render/backend/opengl/openglcontext.h new file mode 100644 index 000000000..9c56f428f --- /dev/null +++ b/app/render/backend/opengl/openglcontext.h @@ -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 . + +***/ + +#ifndef OPENGLCONTEXT_H +#define OPENGLCONTEXT_H + +#include +#include +#include + +#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 texture_params_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // OPENGLCONTEXT_H diff --git a/app/render/backend/opengl/openglframebuffer.cpp b/app/render/backend/opengl/openglframebuffer.cpp deleted file mode 100644 index e31ce50b3..000000000 --- a/app/render/backend/opengl/openglframebuffer.cpp +++ /dev/null @@ -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 . - -***/ - -#include "openglframebuffer.h" - -#include -#include - -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 diff --git a/app/render/backend/opengl/openglframebuffer.h b/app/render/backend/opengl/openglframebuffer.h deleted file mode 100644 index e7d8de9b4..000000000 --- a/app/render/backend/opengl/openglframebuffer.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OPENGLFRAMEBUFFER_H -#define OPENGLFRAMEBUFFER_H - -#include - -#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 diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp deleted file mode 100644 index 1db21e0b3..000000000 --- a/app/render/backend/opengl/openglproxy.cpp +++ /dev/null @@ -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 . - -***/ - -#include "openglproxy.h" - -#include - -#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(stream); - - // Set up OCIO context - QString colorspace_match = video_stream->get_colorspace_match_string(); - - OpenGLColorProcessorPtr color_processor = std::static_pointer_cast(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 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 nv = value.value< QVector >(); - QVector a(nv.size()); - - for (int j=0;j(); - } - - 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()); - } - break; - case NodeInput::kVec3: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kVec4: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kMatrix: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kCombo: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kColor: - { - Color color = value.value(); - - 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(); - - 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(adjusted_width) * relative_pixel_aspect); - } - - shader->setUniformValue(res_param_location, - adjusted_width, - static_cast(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(params.width()), - static_cast(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; iglActiveTexture(GL_TEXTURE0 + i); - functions_->glBindTexture(GL_TEXTURE_2D, textures_to_bind.at(i)); - OpenGLRenderFunctions::PrepareToDraw(functions_); - } - - for (int iteration=0; iterationbind(); - 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(); - - 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 diff --git a/app/render/backend/opengl/openglproxy.h b/app/render/backend/opengl/openglproxy.h deleted file mode 100644 index ff59f76b9..000000000 --- a/app/render/backend/opengl/openglproxy.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OPENGLPROXY_H -#define OPENGLPROXY_H - -#include -#include - -#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 ¶ms); - - 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 ¶ms, - 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 shader_cache_; - - OpenGLTextureCache texture_cache_; - - static OpenGLProxy* instance_; - -private slots: - void FinishInit(); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // OPENGLPROXY_H diff --git a/app/render/backend/opengl/openglrenderfunctions.cpp b/app/render/backend/opengl/openglrenderfunctions.cpp deleted file mode 100644 index fcdc36a64..000000000 --- a/app/render/backend/opengl/openglrenderfunctions.cpp +++ /dev/null @@ -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 . - -***/ - -#include "openglrenderfunctions.h" - -#include -#include -#include - -OLIVE_NAMESPACE_ENTER - -const QVector 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 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 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 &vert, const QVector &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 diff --git a/app/render/backend/opengl/openglrenderfunctions.h b/app/render/backend/opengl/openglrenderfunctions.h deleted file mode 100644 index 7e31987f7..000000000 --- a/app/render/backend/opengl/openglrenderfunctions.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OPENGLFUNCTIONS_H -#define OPENGLFUNCTIONS_H - -#include -#include -#include - -#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& vert, - const QVector& 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 diff --git a/app/render/backend/opengl/openglshader.cpp b/app/render/backend/opengl/openglshader.cpp deleted file mode 100644 index 7e76f619a..000000000 --- a/app/render/backend/opengl/openglshader.cpp +++ /dev/null @@ -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 . - -***/ - -#include "openglshader.h" - -#include -OLIVE_NAMESPACE_ENTER - -OpenGLShaderPtr OpenGLShader::Create() -{ - return std::make_shared(); -} - -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 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 diff --git a/app/render/backend/opengl/openglshader.h b/app/render/backend/opengl/openglshader.h deleted file mode 100644 index 452dc3c3d..000000000 --- a/app/render/backend/opengl/openglshader.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OPENGLSHADER_H -#define OPENGLSHADER_H - -#include -#include - -#include -namespace OCIO = OCIO_NAMESPACE::v1; - -#include "common/define.h" - -OLIVE_NAMESPACE_ENTER - -class OpenGLShader; -using OpenGLShaderPtr = std::shared_ptr; - -/** - * @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 diff --git a/app/render/backend/opengl/opengltexture.cpp b/app/render/backend/opengl/opengltexture.cpp deleted file mode 100644 index 9954e863c..000000000 --- a/app/render/backend/opengl/opengltexture.cpp +++ /dev/null @@ -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 . - -***/ - -#include "opengltexture.h" - -#include -#include -#include - -#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 ¶ms, 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 ¶ms) -{ - 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 diff --git a/app/render/backend/opengl/opengltexture.h b/app/render/backend/opengl/opengltexture.h deleted file mode 100644 index 301383da9..000000000 --- a/app/render/backend/opengl/opengltexture.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OPENGLTEXTURE_H -#define OPENGLTEXTURE_H - -#include -#include - -#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; - -OLIVE_NAMESPACE_EXIT - -Q_DECLARE_METATYPE(OLIVE_NAMESPACE::OpenGLTexturePtr) - -#endif // OPENGLTEXTURE_H diff --git a/app/render/backend/opengl/opengltexturecache.cpp b/app/render/backend/opengl/opengltexturecache.cpp deleted file mode 100644 index e00fc8f42..000000000 --- a/app/render/backend/opengl/opengltexturecache.cpp +++ /dev/null @@ -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 . - -***/ - -#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 ¶ms, 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;iwidth() == 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(); - texture->Create(ctx, params); - } - - texture->SetPixelAspectRatio(params.pixel_aspect_ratio()); - - ReferencePtr ref = std::make_shared(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 ¶ms) -{ - 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 diff --git a/app/render/backend/opengl/opengltexturecache.h b/app/render/backend/opengl/opengltexturecache.h deleted file mode 100644 index 276cdd150..000000000 --- a/app/render/backend/opengl/opengltexturecache.h +++ /dev/null @@ -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 . - -***/ - -#ifndef OPENGLTEXTURECACHE_H -#define OPENGLTEXTURECACHE_H - -#include - -#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; - - 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 available_textures_; - - QList existing_references_; - -}; - -OLIVE_NAMESPACE_EXIT - -Q_DECLARE_METATYPE(OLIVE_NAMESPACE::OpenGLTextureCache::ReferencePtr) - -#endif // OPENGLTEXTURECACHE_H diff --git a/app/render/backend/rendercontext.cpp b/app/render/backend/rendercontext.cpp index a9a4ce73e..2f07702a9 100644 --- a/app/render/backend/rendercontext.cpp +++ b/app/render/backend/rendercontext.cpp @@ -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 . + +***/ + #include "rendercontext.h" -RenderContext::RenderContext() +OLIVE_NAMESPACE_ENTER + +RenderContext::RenderContext(QObject *parent) : + QObject(parent) { } + +QVariant RenderContext::CreateTexture(const VideoParams ¶m) +{ + return CreateTexture(param, nullptr, 0); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/rendercontext.h b/app/render/backend/rendercontext.h index fc443507e..dba4a9e51 100644 --- a/app/render/backend/rendercontext.h +++ b/app/render/backend/rendercontext.h @@ -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 . + +***/ + #ifndef RENDERCONTEXT_H #define RENDERCONTEXT_H +#include +#include -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 diff --git a/app/render/backend/rendercontextthreadwrapper.cpp b/app/render/backend/rendercontextthreadwrapper.cpp new file mode 100644 index 000000000..397189809 --- /dev/null +++ b/app/render/backend/rendercontextthreadwrapper.cpp @@ -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 . + +***/ + +#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 ¶m, 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 diff --git a/app/render/backend/rendercontextthreadwrapper.h b/app/render/backend/rendercontextthreadwrapper.h new file mode 100644 index 000000000..1a8a7f6c1 --- /dev/null +++ b/app/render/backend/rendercontextthreadwrapper.h @@ -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 . + +***/ + +#ifndef RENDERCONTEXTTHREADWRAPPER_H +#define RENDERCONTEXTTHREADWRAPPER_H + +#include + +#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 diff --git a/app/render/backend/renderframebuffer.cpp b/app/render/backend/renderframebuffer.cpp deleted file mode 100644 index dd03c9d59..000000000 --- a/app/render/backend/renderframebuffer.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "renderframebuffer.h" - -RenderFrameBuffer::RenderFrameBuffer() -{ - -} diff --git a/app/render/backend/renderframebuffer.h b/app/render/backend/renderframebuffer.h deleted file mode 100644 index b4b928826..000000000 --- a/app/render/backend/renderframebuffer.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef RENDERFRAMEBUFFER_H -#define RENDERFRAMEBUFFER_H - - -class RenderFrameBuffer -{ -public: - RenderFrameBuffer(); -}; - -#endif // RENDERFRAMEBUFFER_H diff --git a/app/render/backend/rendershader.cpp b/app/render/backend/rendershader.cpp deleted file mode 100644 index a90f3a1e3..000000000 --- a/app/render/backend/rendershader.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "rendershader.h" - -RenderShader::RenderShader() -{ - -} diff --git a/app/render/backend/rendershader.h b/app/render/backend/rendershader.h deleted file mode 100644 index 63f5f97de..000000000 --- a/app/render/backend/rendershader.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef RENDERSHADER_H -#define RENDERSHADER_H - - -class RenderShader -{ -public: - RenderShader(); -}; - -#endif // RENDERSHADER_H diff --git a/app/render/backend/rendertexture.cpp b/app/render/backend/rendertexture.cpp deleted file mode 100644 index 0ba906451..000000000 --- a/app/render/backend/rendertexture.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "rendertexture.h" - -RenderTexture::RenderTexture(RenderContext *ctx) -{ - -} diff --git a/app/render/backend/rendertexture.h b/app/render/backend/rendertexture.h deleted file mode 100644 index 462ad26b9..000000000 --- a/app/render/backend/rendertexture.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef RENDERTEXTURE_H -#define RENDERTEXTURE_H - -#include "rendercontext.h" - -class RenderTexture -{ -public: - RenderTexture(RenderContext* ctx); -}; - -#endif // RENDERTEXTURE_H diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 8e31da39e..b0945e611 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -4,6 +4,7 @@ #include #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(), watcher->GetTicket()->GetJobTime()); + + // Retrieve visual waveforms + QVector waveform_list = watcher->GetTicket()->property("waveforms").value< QVector >(); + 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(it.key()); + break; + } + } + + if (track) { + QList 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); diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index 3c0ff4e26..546b90784 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -22,11 +22,14 @@ #include #include +#include #include #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 ¶ms, const rational &time) @@ -63,13 +61,23 @@ QByteArray RenderManager::Hash(const Node *n, const VideoParams ¶ms, 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(); 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(); @@ -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(); - - 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(ticket->property("viewer")); - rational time = ticket->property("time").value(); - - 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(ticket->property("viewer")); - TimeRange time = ticket->property("time").value(); - - 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(ticket->property("cache")); - FramePtr frame = ticket->property("frame").value(); - 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(ticket->property("viewer")); - - QList 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 diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 035bf7938..68e26905b 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -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_; }; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp new file mode 100644 index 000000000..b3c354c05 --- /dev/null +++ b/app/render/renderprocessor.cpp @@ -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 . + +***/ + +#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(); + + ticket_->Start(); + + switch (type) { + case RenderManager::kTypeVideo: + { + ViewerOutput* viewer = Node::ValueToPtr(ticket_->property("viewer")); + rational time = ticket_->property("time").value(); + + 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(); + 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(ticket_->property("viewer")); + TimeRange time = ticket_->property("time").value(); + + NodeValueTable table = ProcessInput(viewer->samples_input(), time); + + ticket_->Finish(table.Get(NodeParam::kSamples), IsCancelled()); + break; + } + case RenderManager::kTypeVideoDownload: + { + FrameHashCache* cache = Node::ValueToPtr(ticket_->property("cache")); + FramePtr frame = ticket_->property("frame").value(); + 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(ticket_->property("viewer"))->audio_params(); + + QList 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(); + + 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 waveform_list = ticket_->property("waveforms").value< QVector >(); + 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(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(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(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(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 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 nv = value.value< QVector >(); + QVector a(nv.size()); + + for (int j=0;j(); + } + + 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()); + } + break; + case NodeInput::kVec3: + shader->setUniformValue(variable_location, value.value()); + break; + case NodeInput::kVec4: + shader->setUniformValue(variable_location, value.value()); + break; + case NodeInput::kMatrix: + shader->setUniformValue(variable_location, value.value()); + break; + case NodeInput::kCombo: + shader->setUniformValue(variable_location, value.value()); + break; + case NodeInput::kColor: + { + Color color = value.value(); + + 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(); + + 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(adjusted_width) * relative_pixel_aspect); + } + + shader->setUniformValue(res_param_location, + adjusted_width, + static_cast(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(params.width()), + static_cast(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; iglActiveTexture(GL_TEXTURE0 + i); + functions_->glBindTexture(GL_TEXTURE_2D, textures_to_bind.at(i)); + OpenGLRenderFunctions::PrepareToDraw(functions_); + } + + for (int iteration=0; iterationbind(); + 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(ticket_->property("viewer"))->audio_params(); + + for (int i=0;isample_count();i++) { + // Calculate the exact rational time at this sample + double sample_to_second = static_cast(i) / static_cast(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(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::kOffline + && !cache_path_.isEmpty() + && node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) { + const VideoParams& video_params = Node::ValueToPtr(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 diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h new file mode 100644 index 000000000..c7a287b0e --- /dev/null +++ b/app/render/renderprocessor.h @@ -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 . + +***/ + +#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 diff --git a/app/threading/threadpool.h b/app/threading/threadpool.h index b202aeb22..4c9f8d3af 100644 --- a/app/threading/threadpool.h +++ b/app/threading/threadpool.h @@ -69,8 +69,6 @@ public: void RunTicket(RenderTicketPtr ticket); - void Cancel(); - protected: virtual void run() override;