From c14125e640c0e99b8ac7ebb10dee557a5156fbfc Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 1 Nov 2019 22:58:02 +1100 Subject: [PATCH] work towards rewriting the render infrastructure Mostly foundational work and re-implementing older code in a smarter way (i.e. context creation code should be slightly faster than it was before) --- app/render/backend/opengl/CMakeLists.txt | 2 + app/render/backend/opengl/functions.h | 6 +- app/render/backend/opengl/openglbackend.cpp | 113 ++++---------------- app/render/backend/opengl/openglbackend.h | 53 ++------- app/render/backend/opengl/openglshader.h | 3 + app/render/backend/opengl/opengltexture.h | 3 + app/render/backend/opengl/openglworker.cpp | 113 ++++++++++++++++++++ app/render/backend/opengl/openglworker.h | 77 +++++++++++++ app/render/backend/renderbackend.cpp | 2 - app/render/backend/vulkan/vulkanbackend.h | 10 +- app/widget/viewer/viewer.cpp | 2 +- app/widget/viewer/viewerglwidget.h | 1 + 12 files changed, 239 insertions(+), 146 deletions(-) create mode 100644 app/render/backend/opengl/openglworker.cpp create mode 100644 app/render/backend/opengl/openglworker.h diff --git a/app/render/backend/opengl/CMakeLists.txt b/app/render/backend/opengl/CMakeLists.txt index fe3457fb9..9b7f998b1 100644 --- a/app/render/backend/opengl/CMakeLists.txt +++ b/app/render/backend/opengl/CMakeLists.txt @@ -26,5 +26,7 @@ set(OLIVE_SOURCES render/backend/opengl/openglshader.cpp render/backend/opengl/opengltexture.h render/backend/opengl/opengltexture.cpp + render/backend/opengl/openglworker.h + render/backend/opengl/openglworker.cpp PARENT_SCOPE ) diff --git a/app/render/backend/opengl/functions.h b/app/render/backend/opengl/functions.h index 763b5b1e9..f64afe376 100644 --- a/app/render/backend/opengl/functions.h +++ b/app/render/backend/opengl/functions.h @@ -18,8 +18,8 @@ ***/ -#ifndef GLFUNC_H -#define GLFUNC_H +#ifndef OPENGLFUNCTIONS_H +#define OPENGLFUNCTIONS_H #include @@ -50,4 +50,4 @@ void OCIOBlit(OpenGLShaderPtr pipeline, GLuint lut, bool flipped = false, QMatri } } -#endif // GLFUNC_H +#endif // OPENGLFUNCTIONS_H diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index f92d716c3..f26a9dbc6 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -1,12 +1,12 @@ #include "openglbackend.h" +#include #include #include "functions.h" -OpenGLBackend::OpenGLBackend(QOpenGLContext *share_ctx, QObject *parent) : +OpenGLBackend::OpenGLBackend(QObject *parent) : VideoRenderBackend(parent), - share_ctx_(share_ctx), push_time_(-1) { } @@ -18,38 +18,41 @@ OpenGLBackend::~OpenGLBackend() bool OpenGLBackend::Init() { - if (!OpenGLBackend::Init()) { + if (!VideoRenderBackend::Init()) { return false; } - // Some OpenGL implementations (notably wgl) require the context not to be current before sharing. We block the main - // thread here to prevent QOpenGLWidget trying to reclaim the context before we're done - QSurface* old_surface = share_ctx_->surface(); - share_ctx_->doneCurrent(); + QOpenGLContext* share_ctx = QOpenGLContext::currentContext(); + + if (share_ctx == nullptr) { + qCritical() << "No active OpenGL context to connect to"; + return false; + } // Initiate one thread per CPU core for (int i=0;iSetParameters(params()); // Finally, we can move it to its own thread processor->moveToThread(thread); - } - // We've finished creating shared contexts, we can now restore the context to its previous current state - share_ctx_->makeCurrent(old_surface); + // Add processor to list + processors_.append(processor); + + // This function blocks the main thread intentionally. See the documentation for this function to see why. + processor->Init(); + } // Create master texture (the one sent to the viewer) master_texture_ = std::make_shared(); - master_texture_->Create(share_ctx_, params().effective_width(), params().effective_height(), params().format()); + master_texture_->Create(share_ctx, params().effective_width(), params().effective_height(), params().format()); // Create internal FBO for copying textures - copy_buffer_.Create(share_ctx_); + copy_buffer_.Create(share_ctx); copy_buffer_.Attach(master_texture_); copy_pipeline_ = OpenGLShader::CreateDefault(); @@ -77,7 +80,7 @@ void OpenGLBackend::Close() master_texture_ = nullptr; copy_pipeline_ = nullptr; - OpenGLBackend::Close(); + VideoRenderBackend::Close(); } OpenGLTexturePtr OpenGLBackend::GetCachedFrameAsTexture(const rational &time) @@ -127,9 +130,6 @@ bool OpenGLBackend::Compile() void OpenGLBackend::Decompile() { - foreach (const CompiledNode& info, compiled_nodes_) { - delete info.program; - } compiled_nodes_.clear(); } @@ -153,7 +153,7 @@ bool OpenGLBackend::TraverseCompiling(Node *n) CompiledNode compiled_info; compiled_info.id = output_id; - if (!(compiled_info.program = new QOpenGLShaderProgram())) { + if (!(compiled_info.program = std::make_shared())) { SetError("Failed to create OpenGL shader object"); return false; } @@ -188,7 +188,7 @@ bool OpenGLBackend::TraverseCompiling(Node *n) return true; } -QOpenGLShaderProgram* OpenGLBackend::GetShaderFromID(const QString &id) +OpenGLShaderPtr OpenGLBackend::GetShaderFromID(const QString &id) { foreach (const CompiledNode& info, compiled_nodes_) { if (info.id == id) { @@ -307,74 +307,3 @@ void OpenGLBackend::DownloadThreadComplete(const QByteArray &hash) } } } - -OpenGLProcessor::OpenGLProcessor(QOpenGLContext *share_ctx, QObject *parent) : - QObject(parent), - share_ctx_(share_ctx), - ctx_(nullptr), - functions_(nullptr) -{ - surface_.create(); -} - -OpenGLProcessor::~OpenGLProcessor() -{ - surface_.destroy(); -} - -bool OpenGLProcessor::IsStarted() -{ - return ctx_ != nullptr; -} - -void OpenGLProcessor::SetParameters(const VideoRenderingParams &video_params) -{ - video_params_ = video_params; -} - -void OpenGLProcessor::Init() -{ - // Create context object - ctx_ = new QOpenGLContext(); - - // Set share context - ctx_->setShareContext(share_ctx_); - - // Create OpenGL context (automatically destroys any existing if there is one) - if (!ctx_->create()) { - qWarning() << "Failed to create OpenGL context in thread" << thread(); - Close(); - return; - } - - // Make context current on that surface - if (!ctx_->makeCurrent(&surface_)) { - qWarning() << "Failed to makeCurrent() on offscreen surface in thread" << thread(); - Close(); - return; - } - - // Store OpenGL functions instance - functions_ = ctx_->functions(); - - // Set up OpenGL parameters as necessary - functions_->glEnable(GL_BLEND); - UpdateViewportFromParams(); - - buffer_.Create(ctx_); -} - -void OpenGLProcessor::Close() -{ - buffer_.Destroy(); - - functions_ = nullptr; - delete ctx_; -} - -void OpenGLProcessor::UpdateViewportFromParams() -{ - if (functions_ != nullptr && video_params_.is_valid()) { - functions_->glViewport(0, 0, video_params_.effective_width(), video_params_.effective_height()); - } -} diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/backend/opengl/openglbackend.h index 523165ea6..432ed88e2 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/backend/opengl/openglbackend.h @@ -1,56 +1,17 @@ #ifndef OPENGLBACKEND_H #define OPENGLBACKEND_H -#include -#include -#include - #include "../videorenderbackend.h" #include "openglframebuffer.h" +#include "openglworker.h" #include "opengltexture.h" #include "openglshader.h" -class OpenGLProcessor : public QObject { -public: - OpenGLProcessor(QOpenGLContext* share_ctx, QObject* parent = nullptr); - - virtual ~OpenGLProcessor() override; - - Q_DISABLE_COPY_MOVE(OpenGLProcessor) - - bool IsStarted(); - - void SetParameters(const VideoRenderingParams& video_params); - - - -public slots: - void Init(); - - void Close(); - -signals: - - -private: - void UpdateViewportFromParams(); - - QOpenGLContext* share_ctx_; - - QOpenGLContext* ctx_; - QOffscreenSurface surface_; - - QOpenGLFunctions* functions_; - - OpenGLFramebuffer buffer_; - - VideoRenderingParams video_params_; -}; - class OpenGLBackend : public VideoRenderBackend { + Q_OBJECT public: - OpenGLBackend(QOpenGLContext* share_ctx, QObject* parent = nullptr); + OpenGLBackend(QObject* parent = nullptr); virtual ~OpenGLBackend() override; @@ -69,22 +30,20 @@ protected: virtual void GenerateFrame(const rational& time) override; private: - QOpenGLContext* share_ctx_; - struct CompiledNode { QString id; - QOpenGLShaderProgram* program; + OpenGLShaderPtr program; }; bool TraverseCompiling(Node* n); - QOpenGLShaderProgram *GetShaderFromID(const QString& id); + OpenGLShaderPtr GetShaderFromID(const QString& id); QString GenerateShaderID(NodeOutput* output); QList compiled_nodes_; - QVector processors_; + QVector processors_; OpenGLTexturePtr master_texture_; rational push_time_; diff --git a/app/render/backend/opengl/openglshader.h b/app/render/backend/opengl/openglshader.h index 286781d8c..f70de22c2 100644 --- a/app/render/backend/opengl/openglshader.h +++ b/app/render/backend/opengl/openglshader.h @@ -10,6 +10,9 @@ namespace OCIO = OCIO_NAMESPACE::v1; class OpenGLShader; using OpenGLShaderPtr = std::shared_ptr; +/** + * @brief A simple QOpenGLShaderProgram derivative with static functions for creating + */ class OpenGLShader : public QOpenGLShaderProgram { public: OpenGLShader(); diff --git a/app/render/backend/opengl/opengltexture.h b/app/render/backend/opengl/opengltexture.h index f3f41e6f2..952dae5a9 100644 --- a/app/render/backend/opengl/opengltexture.h +++ b/app/render/backend/opengl/opengltexture.h @@ -26,6 +26,9 @@ #include "render/pixelformat.h" +/** + * @brief A class wrapper around an OpenGL texture + */ class OpenGLTexture : public QObject { Q_OBJECT diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp new file mode 100644 index 000000000..1bbbb79dd --- /dev/null +++ b/app/render/backend/opengl/openglworker.cpp @@ -0,0 +1,113 @@ +#include "openglworker.h" + +#include "node/node.h" + +OpenGLWorker::OpenGLWorker(QOpenGLContext *share_ctx, QObject *parent) : + QObject(parent), + share_ctx_(share_ctx), + ctx_(nullptr), + functions_(nullptr) +{ + surface_.create(); +} + +OpenGLWorker::~OpenGLWorker() +{ + surface_.destroy(); +} + +bool OpenGLWorker::IsStarted() +{ + return ctx_ != nullptr; +} + +void OpenGLWorker::SetParameters(const VideoRenderingParams &video_params) +{ + video_params_ = video_params; +} + +void OpenGLWorker::Init() +{ + // Create context object + ctx_ = new QOpenGLContext(); + + // Set share context + ctx_->setShareContext(share_ctx_); + + // Create OpenGL context (automatically destroys any existing if there is one) + if (!ctx_->create()) { + qWarning() << "Failed to create OpenGL context in thread" << thread(); + Close(); + return; + } + + ctx_->moveToThread(this->thread()); + + qDebug() << "Processor initialized in thread" << thread() << "- context is in" << ctx_->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); +} + +void OpenGLWorker::Close() +{ + buffer_.Destroy(); + + functions_ = nullptr; + delete ctx_; +} + +void OpenGLWorker::Render(const NodeDependency &path) +{ + NodeOutput* output = path.node(); + Node* node = output->parent(); + + QList all_deps = node->GetDependencies(); + + // Lock all Nodes to prevent UI changes during this render + foreach (Node* dep, all_deps) { + dep->Lock(); + } + node->Lock(); + + // FIXME: Write traversal code + + // Start OpenGL flushing now while we do clean up work on the CPU + functions_->glFlush(); + + // Unlock all Nodes so changes can be made again + foreach (Node* dep, all_deps) { + dep->Unlock(); + } + node->Unlock(); + + // Now we need the texture done so we call glFinish() + functions_->glFinish(); +} + +void OpenGLWorker::UpdateViewportFromParams() +{ + if (functions_ != nullptr && video_params_.is_valid()) { + functions_->glViewport(0, 0, video_params_.effective_width(), video_params_.effective_height()); + } +} + +void OpenGLWorker::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(); + + // Set up OpenGL parameters as necessary + functions_->glEnable(GL_BLEND); + UpdateViewportFromParams(); + + buffer_.Create(ctx_); + + qDebug() << "Context in" << ctx_->thread() << "successfully finished"; +} diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h new file mode 100644 index 000000000..e611254ef --- /dev/null +++ b/app/render/backend/opengl/openglworker.h @@ -0,0 +1,77 @@ +#ifndef OPENGLPROCESSOR_H +#define OPENGLPROCESSOR_H + +#include +#include +#include + +#include "node/dependency.h" +#include "openglframebuffer.h" +#include "render/videoparams.h" + +class OpenGLWorker : public QObject { + Q_OBJECT +public: + OpenGLWorker(QOpenGLContext* share_ctx, QObject* parent = nullptr); + + virtual ~OpenGLWorker() override; + + Q_DISABLE_COPY_MOVE(OpenGLWorker) + + bool IsStarted(); + + void SetParameters(const VideoRenderingParams& video_params); + + /** + * @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). + */ + void Init(); + +public slots: + void Close(); + + void Render(const NodeDependency& path); + +signals: + +private: + void ProcessNode(); + + void UpdateViewportFromParams(); + + QOpenGLContext* share_ctx_; + + QOpenGLContext* ctx_; + QOffscreenSurface surface_; + + QOpenGLFunctions* functions_; + + OpenGLFramebuffer buffer_; + + VideoRenderingParams video_params_; + +private slots: + void FinishInit(); + + void RenderAsSibling(const NodeDependency& path); +}; + +#endif // OPENGLPROCESSOR_H diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index db3274eaa..ce3779bad 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -19,14 +19,12 @@ const QString &RenderBackend::GetError() const void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) { if (viewer_node_ != nullptr) { - disconnect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(Compile())); disconnect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&))); } viewer_node_ = viewer_node; if (viewer_node_ != nullptr) { - connect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(Compile())); connect(viewer_node_, SIGNAL(TextureChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&))); } diff --git a/app/render/backend/vulkan/vulkanbackend.h b/app/render/backend/vulkan/vulkanbackend.h index 1f04dcce6..dd3f4a118 100644 --- a/app/render/backend/vulkan/vulkanbackend.h +++ b/app/render/backend/vulkan/vulkanbackend.h @@ -1,7 +1,15 @@ #ifndef VULKANBACKEND_H #define VULKANBACKEND_H - +/** + * @brief A Vulkan-based variant of the rendering engine + * + * I literally know nothing about Vulkan but maybe one day I will and can fill this out. Also keep in mind projects + * that can cross-compile GLSL to SPIR like these: + * + * https://github.com/KhronosGroup/glslang + * https://github.com/septag/glslcc + */ class VulkanBackend { public: diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 486138ba5..f3352f9a8 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -84,7 +84,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : ruler_->SetScale(48.0); // Start background renderers - video_renderer_ = new OpenGLBackend(gl_widget_->context(), this); + video_renderer_ = new OpenGLBackend(this); connect(video_renderer_, SIGNAL(CachedFrameReady(const rational&)), this, SLOT(RendererCachedFrame(const rational&))); } diff --git a/app/widget/viewer/viewerglwidget.h b/app/widget/viewer/viewerglwidget.h index 969339562..1544db1e2 100644 --- a/app/widget/viewer/viewerglwidget.h +++ b/app/widget/viewer/viewerglwidget.h @@ -100,6 +100,7 @@ protected: * Simple OpenGL drawing function for painting the texture on screen. Standardized around OpenGL ES 3.2 Core. */ virtual void paintGL() override; + private: /** * @brief Creates the render pipeline shader