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)
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
***/
|
||||
|
||||
#ifndef GLFUNC_H
|
||||
#define GLFUNC_H
|
||||
#ifndef OPENGLFUNCTIONS_H
|
||||
#define OPENGLFUNCTIONS_H
|
||||
|
||||
#include <QMatrix4x4>
|
||||
|
||||
@@ -50,4 +50,4 @@ void OCIOBlit(OpenGLShaderPtr pipeline, GLuint lut, bool flipped = false, QMatri
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLFUNC_H
|
||||
#endif // OPENGLFUNCTIONS_H
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "openglbackend.h"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QThread>
|
||||
|
||||
#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;i<threads().size();i++) {
|
||||
QThread* thread = threads().at(i);
|
||||
|
||||
// Create one processor object for each thread
|
||||
OpenGLProcessor* processor = new OpenGLProcessor(share_ctx_, thread);
|
||||
|
||||
// FIXME: Hardcoded values
|
||||
OpenGLWorker* processor = new OpenGLWorker(share_ctx);
|
||||
processor->SetParameters(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<OpenGLTexture>();
|
||||
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<OpenGLShader>())) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +1,17 @@
|
||||
#ifndef OPENGLBACKEND_H
|
||||
#define OPENGLBACKEND_H
|
||||
|
||||
#include <memory>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLShaderProgram>
|
||||
|
||||
#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<CompiledNode> compiled_nodes_;
|
||||
|
||||
QVector<OpenGLProcessor*> processors_;
|
||||
QVector<OpenGLWorker*> processors_;
|
||||
|
||||
OpenGLTexturePtr master_texture_;
|
||||
rational push_time_;
|
||||
|
||||
@@ -10,6 +10,9 @@ namespace OCIO = OCIO_NAMESPACE::v1;
|
||||
class OpenGLShader;
|
||||
using OpenGLShaderPtr = std::shared_ptr<OpenGLShader>;
|
||||
|
||||
/**
|
||||
* @brief A simple QOpenGLShaderProgram derivative with static functions for creating
|
||||
*/
|
||||
class OpenGLShader : public QOpenGLShaderProgram {
|
||||
public:
|
||||
OpenGLShader();
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
|
||||
#include "render/pixelformat.h"
|
||||
|
||||
/**
|
||||
* @brief A class wrapper around an OpenGL texture
|
||||
*/
|
||||
class OpenGLTexture : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -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<Node*> 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";
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef OPENGLPROCESSOR_H
|
||||
#define OPENGLPROCESSOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
|
||||
#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
|
||||
@@ -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&)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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&)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user