From 91f1be90a998d42351e04df6e2e87536641700f9 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 3 Feb 2020 17:30:58 +1100 Subject: [PATCH] opengl: isolate all functions to one thread --- app/render/backend/opengl/CMakeLists.txt | 2 + app/render/backend/opengl/openglbackend.cpp | 97 ++--- app/render/backend/opengl/openglbackend.h | 14 +- app/render/backend/opengl/openglproxy.cpp | 411 ++++++++++++++++++++ app/render/backend/opengl/openglproxy.h | 73 ++++ app/render/backend/opengl/openglworker.cpp | 373 +----------------- app/render/backend/opengl/openglworker.h | 56 +-- app/render/backend/videorenderbackend.cpp | 3 +- 8 files changed, 532 insertions(+), 497 deletions(-) create mode 100644 app/render/backend/opengl/openglproxy.cpp create mode 100644 app/render/backend/opengl/openglproxy.h diff --git a/app/render/backend/opengl/CMakeLists.txt b/app/render/backend/opengl/CMakeLists.txt index 1911e4d73..e00ae7620 100644 --- a/app/render/backend/opengl/CMakeLists.txt +++ b/app/render/backend/opengl/CMakeLists.txt @@ -24,6 +24,8 @@ set(OLIVE_SOURCES render/backend/opengl/openglexporter.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 diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index bab0e1a28..f20279810 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -7,7 +7,8 @@ OpenGLBackend::OpenGLBackend(QObject *parent) : VideoRenderBackend(parent), - master_texture_(nullptr) + master_texture_(nullptr), + proxy_(nullptr) { } @@ -22,30 +23,41 @@ bool OpenGLBackend::InitInternal() return false; } - QOpenGLContext* share_ctx = QOpenGLContext::currentContext(); + proxy_ = new OpenGLProxy(); + proxy_->SetParameters(params()); + QThread* proxy_thread = new QThread(); + proxy_thread->start(QThread::LowPriority); + proxy_->moveToThread(proxy_thread); - if (share_ctx == nullptr) { - qCritical() << "No active OpenGL context to connect to"; + if (!proxy_->Init()) { + proxy_thread->quit(); + proxy_thread->wait(); + delete proxy_thread; + delete proxy_; return false; } // Initiate one thread per CPU core for (int i=0;iSetParameters(params()); processors_.append(processor); + + connect(processor, &OpenGLWorker::RequestFrameToValue, proxy_, &OpenGLProxy::FrameToValue, Qt::BlockingQueuedConnection); + connect(processor, &OpenGLWorker::RequestTextureToBuffer, proxy_, &OpenGLProxy::TextureToBuffer, Qt::BlockingQueuedConnection); + connect(processor, &OpenGLWorker::RequestRunNodeAccelerated, proxy_, &OpenGLProxy::RunNodeAccelerated, Qt::BlockingQueuedConnection); } // Create master texture (the one sent to the viewer) master_texture_ = std::make_shared(); - master_texture_->Create(share_ctx, + master_texture_->Create(QOpenGLContext::currentContext(), params().effective_width(), params().effective_height(), params().format()); // Create copy buffer/pipeline - copy_buffer_.Create(share_ctx); + copy_buffer_.Create(QOpenGLContext::currentContext()); copy_pipeline_ = OpenGLShader::CreateDefault(); return true; @@ -53,6 +65,11 @@ bool OpenGLBackend::InitInternal() void OpenGLBackend::CloseInternal() { + if (proxy_) { + delete proxy_; + proxy_ = nullptr; + } + copy_buffer_.Destroy(); copy_pipeline_ = nullptr; master_texture_ = nullptr; @@ -72,75 +89,11 @@ OpenGLTexturePtr OpenGLBackend::GetCachedFrameAsTexture(const rational &time) bool OpenGLBackend::CompileInternal() { - if (!viewer_node() || !viewer_node()->texture_input()->IsConnected()) { - // Nothing to be done, nothing to compile - return true; - } - - // Traverse node graph compiling where necessary - - QList nodes = viewer_node()->GetDependencies(); - - foreach (Node* n, nodes) { - // Check if we have a shader or not - if (!shader_cache_.Has(n->id())) { - // Since we don't have a shader, compile one now - - // If the node has no code, it mustn't be GPU accelerated - if (!n->IsAccelerated()) { - // We enter a null shader so we don't try to compile this again - shader_cache_.Add(n->id(), nullptr); - } else { - // Since we have shader code, compile it now - OpenGLShaderPtr program; - - QString frag_code = n->AcceleratedCodeFragment(); - QString vert_code = n->AcceleratedCodeVertex(); - - if (frag_code.isEmpty()) { - frag_code = OpenGLShader::CodeDefaultFragment(); - } - - if (vert_code.isEmpty()) { - vert_code = OpenGLShader::CodeDefaultVertex(); - } - - if (!(program = std::make_shared())) { - SetError(QStringLiteral("Failed to create OpenGL shader object")); - return false; - } - - if (!program->create()) { - SetError(QStringLiteral("Failed to create OpenGL shader on device")); - return false; - } - - if (!program->addShaderFromSourceCode(QOpenGLShader::Fragment, frag_code)) { - SetError(QStringLiteral("Failed to add OpenGL fragment shader code")); - return false; - } - - if (!program->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_code)) { - SetError(QStringLiteral("Failed to add OpenGL vertex shader code")); - return false; - } - - if (!program->link()) { - SetError(QStringLiteral("Failed to compile OpenGL shader: %1").arg(program->log())); - return false; - } - - shader_cache_.Add(n->id(), program); - } - } - } - return true; } void OpenGLBackend::DecompileInternal() { - shader_cache_.Clear(); } void OpenGLBackend::EmitCachedFrameReady(const rational &time, const QVariant &value, qint64 job_time) @@ -166,6 +119,8 @@ void OpenGLBackend::ParamsChangedEvent() params().effective_width(), params().effective_height(), params().format()); + + proxy_->SetParameters(params()); } } diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/backend/opengl/openglbackend.h index 2959cd993..d802fb9e5 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/backend/opengl/openglbackend.h @@ -2,12 +2,12 @@ #define OPENGLBACKEND_H #include "../videorenderbackend.h" +#include "openglbackend.h" #include "openglframebuffer.h" -#include "openglworker.h" -#include "opengltexture.h" -#include "opengltexturecache.h" +#include "openglproxy.h" #include "openglshader.h" -#include "openglshadercache.h" +#include "opengltexture.h" +#include "openglworker.h" class OpenGLBackend : public VideoRenderBackend { @@ -35,15 +35,13 @@ protected: private: OpenGLTexturePtr CopyTexture(OpenGLTexturePtr input); - OpenGLShaderCache shader_cache_; - - OpenGLTextureCache texture_cache_; - OpenGLTexturePtr master_texture_; OpenGLFramebuffer copy_buffer_; OpenGLShaderPtr copy_pipeline_; + OpenGLProxy* proxy_; + }; #endif // OPENGLBACKEND_H diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp new file mode 100644 index 000000000..5f26e9941 --- /dev/null +++ b/app/render/backend/opengl/openglproxy.cpp @@ -0,0 +1,411 @@ +#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/pixelservice.h" + +OpenGLProxy::OpenGLProxy(QObject *parent) : + QObject(parent), + ctx_(nullptr), + functions_(nullptr) +{ + surface_.create(); +} + +OpenGLProxy::~OpenGLProxy() +{ + surface_.destroy(); +} + +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; +} + +void OpenGLProxy::FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable *table) +{ + // Ensure stream is video or image type + if (stream->type() != Stream::kVideo && stream->type() != Stream::kImage) { + return; + } + + ImageStreamPtr video_stream = std::static_pointer_cast(stream); + + // Set up OCIO context + OpenGLColorProcessorPtr color_processor = std::static_pointer_cast(color_cache_.Get(video_stream->colorspace())); + + if (!color_processor) { + // FIXME: We match with the colorspace string, but this won't change if the user sets a new config with a colorspace with the same string + color_processor = OpenGLColorProcessor::CreateOpenGL(video_stream->footage()->project()->color_manager()->GetConfig(), + video_stream->colorspace(), + OCIO::ROLE_SCENE_LINEAR); + color_cache_.Add(video_stream->colorspace(), color_processor); + } + + ColorManager::OCIOMethod ocio_method = ColorManager::GetOCIOMethodForMode(video_params_.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) { + // If alpha is associated, disassociate for the color transform + if (video_stream->premultiplied_alpha()) { + ColorManager::DisassociateAlpha(frame); + } + + // Convert frame to float for OCIO + frame = PixelService::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); + + // Perform color transform + color_processor->ConvertFrame(frame); + + // Associate alpha + if (video_stream->premultiplied_alpha()) { + ColorManager::ReassociateAlpha(frame); + } else { + ColorManager::AssociateAlpha(frame); + } + } + + VideoRenderingParams footage_params(frame->width(), frame->height(), stream->timebase(), frame->format(), video_params_.mode()); + + OpenGLTextureCache::ReferencePtr footage_tex_ref = texture_cache_.Get(ctx_, footage_params, frame->data()); + + if (ocio_method == ColorManager::kOCIOFast) { + if (!color_processor->IsEnabled()) { + color_processor->Enable(ctx_, video_stream->premultiplied_alpha()); + } + + // Check frame aspect ratio + if (frame->sample_aspect_ratio() != 1 && frame->sample_aspect_ratio() != 0) { + int new_width = frame->width(); + int new_height = frame->height(); + + // Scale the frame in a way that does not reduce the resolution + if (frame->sample_aspect_ratio() > 1) { + // Make wider + new_width = qRound(static_cast(new_width) * frame->sample_aspect_ratio().toDouble()); + } else { + // Make taller + new_height = qRound(static_cast(new_height) / frame->sample_aspect_ratio().toDouble()); + } + + footage_params = VideoRenderingParams(new_width, + new_height, + footage_params.time_base(), + footage_params.format(), + footage_params.mode()); + } + + // Create destination texture + OpenGLTextureCache::ReferencePtr associated_tex_ref = texture_cache_.Get(ctx_, footage_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; + } + + table->Push(NodeParam::kTexture, QVariant::fromValue(footage_tex_ref)); +} + +void OpenGLProxy::Close() +{ + shader_cache_.Clear(); + buffer_.Destroy(); + functions_ = nullptr; + delete ctx_; +} + +void OpenGLProxy::RunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable *output_params) +{ + if (!node->IsAccelerated()) { + return; + } + + OpenGLShaderPtr shader = shader_cache_.Get(node->id()); + + if (!shader) { + // Since we have shader code, compile it now + + QString frag_code = node->AcceleratedCodeFragment(); + QString vert_code = node->AcceleratedCodeVertex(); + + if (frag_code.isEmpty()) { + frag_code = OpenGLShader::CodeDefaultFragment(); + } + + if (vert_code.isEmpty()) { + vert_code = OpenGLShader::CodeDefaultVertex(); + } + + shader = std::make_shared(); + shader->create(); + shader->addShaderFromSourceCode(QOpenGLShader::Fragment, frag_code); + shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vert_code); + shader->link(); + + shader_cache_.Add(node->id(), shader); + } + + // Create the output textures + QList dst_refs; + dst_refs.append(texture_cache_.Get(ctx_, video_params_)); + GLuint iterative_input = 0; + + // If this node requires multiple iterations, get a texture for it too + if (node->AcceleratedCodeIterations() > 1 && node->AcceleratedCodeIterativeInput()) { + dst_refs.append(texture_cache_.Get(ctx_, video_params_)); + } + + // Lock the shader so no other thread interferes as we set parameters and draw (and we don't interfere with any others) + shader->bind(); + + unsigned int input_texture_count = 0; + + foreach (NodeParam* param, node->parameters()) { + if (param->type() == NodeParam::kInput) { + // See if the shader has takes this parameter as an input + int variable_location = shader->uniformLocation(param->id()); + + if (variable_location > -1) { + // This variable is used in the shader, let's set it to our value + + NodeInput* input = static_cast(param); + + // Get value from database at this input + const NodeValueTable& input_data = input_params[input]; + + QVariant value = node->InputValueFromTable(input, input_data); + + switch (input->data_type()) { + case NodeInput::kInt: + shader->setUniformValue(variable_location, value.toInt()); + break; + case NodeInput::kFloat: + shader->setUniformValue(variable_location, value.toFloat()); + break; + case NodeInput::kVec2: + 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::kColor: + shader->setUniformValue(variable_location, value.value()); + break; + case NodeInput::kBoolean: + shader->setUniformValue(variable_location, value.toBool()); + break; + case NodeInput::kFootage: + case NodeInput::kTexture: + case NodeInput::kBuffer: + { + OpenGLTextureCache::ReferencePtr texture = value.value(); + + functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count); + + GLuint tex_id = texture ? texture->texture()->texture() : 0; + functions_->glBindTexture(GL_TEXTURE_2D, tex_id); + + // Set value to bound texture + shader->setUniformValue(variable_location, input_texture_count); + + // Set enable flag if shader wants it + int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(input->id())); + if (enable_param_location > -1) { + shader->setUniformValue(enable_param_location, + tex_id > 0); + } + + if (tex_id > 0) { + // Set texture resolution if shader wants it + int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(input->id())); + if (res_param_location > -1) { + shader->setUniformValue(res_param_location, + static_cast(texture->texture()->width()), + static_cast(texture->texture()->height())); + } + } + + // If this texture binding is the iterative input, set it here + if (input == node->AcceleratedCodeIterativeInput()) { + iterative_input = input_texture_count; + } + + OpenGLRenderFunctions::PrepareToDraw(functions_); + + input_texture_count++; + break; + } + case NodeInput::kSamples: + case NodeInput::kText: + case NodeInput::kRational: + case NodeInput::kFont: + case NodeInput::kFile: + case NodeInput::kDecimal: + case NodeInput::kWholeNumber: + case NodeInput::kNumber: + case NodeInput::kString: + case NodeInput::kVector: + case NodeInput::kNone: + case NodeInput::kAny: + break; + } + } + } + } + + // Set up OpenGL parameters as necessary + functions_->glViewport(0, 0, video_params_.effective_width(), video_params_.effective_height()); + + // Provide some standard args + shader->setUniformValue("ove_resolution", + static_cast(video_params_.width()), + static_cast(video_params_.height())); + + if (node->IsBlock() && static_cast(node)->type() == Block::kTransition) { + const TransitionBlock* transition_node = static_cast(node); + + // Provides total transition progress from 0.0 (start) - 1.0 (end) + shader->setUniformValue("ove_tprog_all", static_cast(transition_node->GetTotalProgress(range.in()))); + + // Provides progress of out section from 1.0 (start) - 0.0 (end) + shader->setUniformValue("ove_tprog_out", static_cast(transition_node->GetOutProgress(range.in()))); + + // Provides progress of in section from 0.0 (start) - 1.0 (end) + shader->setUniformValue("ove_tprog_in", static_cast(transition_node->GetInProgress(range.in()))); + } + + // Some nodes use multiple iterations for optimization + OpenGLTextureCache::ReferencePtr output_tex; + + for (int iteration=0;iterationAcceleratedCodeIterations();iteration++) { + // If this is not the first iteration, set the parameter that will receive the last iteration's texture + OpenGLTextureCache::ReferencePtr source_tex = dst_refs.at((iteration+1)%dst_refs.size()); + OpenGLTextureCache::ReferencePtr destination_tex = dst_refs.at(iteration%dst_refs.size()); + + // Set iteration number + shader->bind(); + shader->setUniformValue("ove_iteration", iteration); + shader->release(); + + if (iteration > 0) { + functions_->glActiveTexture(GL_TEXTURE0 + iterative_input); + functions_->glBindTexture(GL_TEXTURE_2D, source_tex->texture()->texture()); + } + + buffer_.Attach(destination_tex->texture(), true); + buffer_.Bind(); + + // Blit this texture through this shader + OpenGLRenderFunctions::Blit(shader); + + buffer_.Release(); + buffer_.Detach(); + + // Update output reference to the last texture we wrote to + output_tex = destination_tex; + } + + // Release any textures we bound before + while (input_texture_count > 0) { + input_texture_count--; + + // Release texture here + functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count); + functions_->glBindTexture(GL_TEXTURE_2D, 0); + } + + shader->release(); + + output_params->Push(NodeParam::kTexture, QVariant::fromValue(output_tex)); +} + +void OpenGLProxy::TextureToBuffer(const QVariant &tex_in, QByteArray &buffer) +{ + OpenGLTextureCache::ReferencePtr texture = tex_in.value(); + + PixelFormat::Info format_info = PixelService::GetPixelFormatInfo(video_params_.format()); + + texture->texture()->Lock(); + + QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions(); + buffer_.Attach(texture->texture()); + buffer_.Bind(); + + f->glReadPixels(0, + 0, + video_params_.effective_width(), + video_params_.effective_height(), + format_info.pixel_format, + format_info.gl_pixel_type, + buffer.data()); + + buffer_.Release(); + buffer_.Detach(); + + texture->texture()->Unlock(); +} + +void OpenGLProxy::SetParameters(const VideoRenderingParams ¶ms) +{ + video_params_ = params; + + if (functions_ != nullptr && video_params_.is_valid()) { + functions_->glViewport(0, 0, video_params_.effective_width(), video_params_.effective_height()); + } +} + +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); + + SetParameters(video_params_); + + buffer_.Create(ctx_); +} diff --git a/app/render/backend/opengl/openglproxy.h b/app/render/backend/opengl/openglproxy.h new file mode 100644 index 000000000..0faf68b13 --- /dev/null +++ b/app/render/backend/opengl/openglproxy.h @@ -0,0 +1,73 @@ +#ifndef OPENGLPROXY_H +#define OPENGLPROXY_H + +#include +#include + +#include "../videorenderworker.h" +#include "openglframebuffer.h" +#include "openglshadercache.h" +#include "opengltexturecache.h" + +class OpenGLProxy : public QObject { + Q_OBJECT +public: + OpenGLProxy(QObject* parent = nullptr); + + virtual ~OpenGLProxy() override; + + /** + * @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(); + + void FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable* table); + + void RunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable* output_params); + + void TextureToBuffer(const QVariant& texture, QByteArray& buffer); + + void SetParameters(const VideoRenderingParams& params); + +private: + QOpenGLContext* ctx_; + QOffscreenSurface surface_; + + QOpenGLFunctions* functions_; + + OpenGLFramebuffer buffer_; + + ColorProcessorCache color_cache_; + + VideoRenderingParams video_params_; + + OpenGLShaderCache shader_cache_; + + OpenGLTextureCache texture_cache_; + +private slots: + void FinishInit(); + +}; + +#endif // OPENGLPROXY_H diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index 83eb39587..c357eaaa5 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -9,385 +9,22 @@ #include "render/colormanager.h" #include "render/pixelservice.h" -OpenGLWorker::OpenGLWorker(QOpenGLContext *share_ctx, OpenGLShaderCache *shader_cache, OpenGLTextureCache *texture_cache, VideoRenderFrameCache *frame_cache, QObject *parent) : - VideoRenderWorker(frame_cache, parent), - share_ctx_(share_ctx), - ctx_(nullptr), - functions_(nullptr), - shader_cache_(shader_cache), - texture_cache_(texture_cache) +OpenGLWorker::OpenGLWorker(VideoRenderFrameCache *frame_cache, QObject *parent) : + VideoRenderWorker(frame_cache, parent) { - surface_.create(); -} - -OpenGLWorker::~OpenGLWorker() -{ - surface_.destroy(); -} - -bool OpenGLWorker::InitInternal() -{ - if (!VideoRenderWorker::InitInternal()) { - return false; - } - - // 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(); - 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; } void OpenGLWorker::FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable *table) { - // Ensure stream is video or image type - if (stream->type() != Stream::kVideo && stream->type() != Stream::kImage) { - return; - } - - ImageStreamPtr video_stream = std::static_pointer_cast(stream); - - // Set up OCIO context - OpenGLColorProcessorPtr color_processor = std::static_pointer_cast(color_cache()->Get(video_stream->colorspace())); - - if (!color_processor) { - // FIXME: We match with the colorspace string, but this won't change if the user sets a new config with a colorspace with the same string - color_processor = OpenGLColorProcessor::CreateOpenGL(video_stream->footage()->project()->color_manager()->GetConfig(), - video_stream->colorspace(), - OCIO::ROLE_SCENE_LINEAR); - color_cache()->Add(video_stream->colorspace(), color_processor); - } - - ColorManager::OCIOMethod ocio_method = ColorManager::GetOCIOMethodForMode(video_params().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) { - // If alpha is associated, disassociate for the color transform - if (video_stream->premultiplied_alpha()) { - ColorManager::DisassociateAlpha(frame); - } - - // Convert frame to float for OCIO - frame = PixelService::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F); - - // Perform color transform - color_processor->ConvertFrame(frame); - - // Associate alpha - if (video_stream->premultiplied_alpha()) { - ColorManager::ReassociateAlpha(frame); - } else { - ColorManager::AssociateAlpha(frame); - } - } - - VideoRenderingParams footage_params(frame->width(), frame->height(), stream->timebase(), frame->format(), video_params().mode()); - - OpenGLTextureCache::ReferencePtr footage_tex_ref = texture_cache_->Get(ctx_, footage_params, frame->data()); - - if (ocio_method == ColorManager::kOCIOFast) { - if (!color_processor->IsEnabled()) { - color_processor->Enable(ctx_, video_stream->premultiplied_alpha()); - } - - // Check frame aspect ratio - if (frame->sample_aspect_ratio() != 1 && frame->sample_aspect_ratio() != 0) { - int new_width = frame->width(); - int new_height = frame->height(); - - // Scale the frame in a way that does not reduce the resolution - if (frame->sample_aspect_ratio() > 1) { - // Make wider - new_width = qRound(static_cast(new_width) * frame->sample_aspect_ratio().toDouble()); - } else { - // Make taller - new_height = qRound(static_cast(new_height) / frame->sample_aspect_ratio().toDouble()); - } - - footage_params = VideoRenderingParams(new_width, - new_height, - footage_params.time_base(), - footage_params.format(), - footage_params.mode()); - } - - // Create destination texture - OpenGLTextureCache::ReferencePtr associated_tex_ref = texture_cache_->Get(ctx_, footage_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; - } - - table->Push(NodeParam::kTexture, QVariant::fromValue(footage_tex_ref)); -} - -void OpenGLWorker::CloseInternal() -{ - buffer_.Destroy(); - functions_ = nullptr; - delete ctx_; -} - -void OpenGLWorker::ParametersChangedEvent() -{ - if (functions_ != nullptr && video_params().is_valid()) { - functions_->glViewport(0, 0, video_params().effective_width(), video_params().effective_height()); - } + emit RequestFrameToValue(stream, frame, table); } void OpenGLWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable *output_params) { - OpenGLShaderPtr shader = shader_cache_->Get(node->id()); - - if (!shader) { - return; - } - - // Create the output textures - QList dst_refs; - dst_refs.append(texture_cache_->Get(ctx_, video_params())); - GLuint iterative_input = 0; - - // If this node requires multiple iterations, get a texture for it too - if (node->AcceleratedCodeIterations() > 1 && node->AcceleratedCodeIterativeInput()) { - dst_refs.append(texture_cache_->Get(ctx_, video_params())); - } - - // Lock the shader so no other thread interferes as we set parameters and draw (and we don't interfere with any others) - shader->bind(); - - unsigned int input_texture_count = 0; - - foreach (NodeParam* param, node->parameters()) { - if (param->type() == NodeParam::kInput) { - // See if the shader has takes this parameter as an input - int variable_location = shader->uniformLocation(param->id()); - - if (variable_location > -1) { - // This variable is used in the shader, let's set it to our value - - NodeInput* input = static_cast(param); - - // Get value from database at this input - const NodeValueTable& input_data = input_params[input]; - - QVariant value = node->InputValueFromTable(input, input_data); - - switch (input->data_type()) { - case NodeInput::kInt: - shader->setUniformValue(variable_location, value.toInt()); - break; - case NodeInput::kFloat: - shader->setUniformValue(variable_location, value.toFloat()); - break; - case NodeInput::kVec2: - 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::kColor: - shader->setUniformValue(variable_location, value.value()); - break; - case NodeInput::kBoolean: - shader->setUniformValue(variable_location, value.toBool()); - break; - case NodeInput::kFootage: - case NodeInput::kTexture: - case NodeInput::kBuffer: - { - OpenGLTextureCache::ReferencePtr texture = value.value(); - - functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count); - - GLuint tex_id = texture ? texture->texture()->texture() : 0; - functions_->glBindTexture(GL_TEXTURE_2D, tex_id); - - // Set value to bound texture - shader->setUniformValue(variable_location, input_texture_count); - - // Set enable flag if shader wants it - int enable_param_location = shader->uniformLocation(QStringLiteral("%1_enabled").arg(input->id())); - if (enable_param_location > -1) { - shader->setUniformValue(enable_param_location, - tex_id > 0); - } - - if (tex_id > 0) { - // Set texture resolution if shader wants it - int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(input->id())); - if (res_param_location > -1) { - shader->setUniformValue(res_param_location, - static_cast(texture->texture()->width()), - static_cast(texture->texture()->height())); - } - } - - // If this texture binding is the iterative input, set it here - if (input == node->AcceleratedCodeIterativeInput()) { - iterative_input = input_texture_count; - } - - OpenGLRenderFunctions::PrepareToDraw(functions_); - - input_texture_count++; - break; - } - case NodeInput::kSamples: - case NodeInput::kText: - case NodeInput::kRational: - case NodeInput::kFont: - case NodeInput::kFile: - case NodeInput::kDecimal: - case NodeInput::kWholeNumber: - case NodeInput::kNumber: - case NodeInput::kString: - case NodeInput::kVector: - case NodeInput::kNone: - case NodeInput::kAny: - break; - } - } - } - } - - // Set up OpenGL parameters as necessary - functions_->glViewport(0, 0, video_params().effective_width(), video_params().effective_height()); - - // Provide some standard args - shader->setUniformValue("ove_resolution", - static_cast(video_params().width()), - static_cast(video_params().height())); - - if (node->IsBlock() && static_cast(node)->type() == Block::kTransition) { - const TransitionBlock* transition_node = static_cast(node); - - // Provides total transition progress from 0.0 (start) - 1.0 (end) - shader->setUniformValue("ove_tprog_all", static_cast(transition_node->GetTotalProgress(range.in()))); - - // Provides progress of out section from 1.0 (start) - 0.0 (end) - shader->setUniformValue("ove_tprog_out", static_cast(transition_node->GetOutProgress(range.in()))); - - // Provides progress of in section from 0.0 (start) - 1.0 (end) - shader->setUniformValue("ove_tprog_in", static_cast(transition_node->GetInProgress(range.in()))); - } - - // Some nodes use multiple iterations for optimization - OpenGLTextureCache::ReferencePtr output_tex; - - for (int iteration=0;iterationAcceleratedCodeIterations();iteration++) { - // If this is not the first iteration, set the parameter that will receive the last iteration's texture - OpenGLTextureCache::ReferencePtr source_tex = dst_refs.at((iteration+1)%dst_refs.size()); - OpenGLTextureCache::ReferencePtr destination_tex = dst_refs.at(iteration%dst_refs.size()); - - // Set iteration number - shader->bind(); - shader->setUniformValue("ove_iteration", iteration); - shader->release(); - - if (iteration > 0) { - functions_->glActiveTexture(GL_TEXTURE0 + iterative_input); - functions_->glBindTexture(GL_TEXTURE_2D, source_tex->texture()->texture()); - } - - buffer_.Attach(destination_tex->texture(), true); - buffer_.Bind(); - - // Blit this texture through this shader - OpenGLRenderFunctions::Blit(shader); - - buffer_.Release(); - buffer_.Detach(); - - // Update output reference to the last texture we wrote to - output_tex = destination_tex; - } - - // Release any textures we bound before - while (input_texture_count > 0) { - input_texture_count--; - - // Release texture here - functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count); - functions_->glBindTexture(GL_TEXTURE_2D, 0); - } - - shader->release(); - - output_params->Push(NodeParam::kTexture, QVariant::fromValue(output_tex)); + emit RequestRunNodeAccelerated(node, range, input_params, output_params); } void OpenGLWorker::TextureToBuffer(const QVariant &tex_in, QByteArray &buffer) { - OpenGLTextureCache::ReferencePtr texture = tex_in.value(); - - PixelFormat::Info format_info = PixelService::GetPixelFormatInfo(video_params().format()); - - texture->texture()->Lock(); - - QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions(); - buffer_.Attach(texture->texture()); - buffer_.Bind(); - - f->glReadPixels(0, - 0, - video_params().effective_width(), - video_params().effective_height(), - format_info.pixel_format, - format_info.gl_pixel_type, - buffer.data()); - - buffer_.Release(); - buffer_.Detach(); - - texture->texture()->Unlock(); -} - -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(); - functions_->glBlendFunc(GL_ONE, GL_ZERO); - - ParametersChangedEvent(); - - buffer_.Create(ctx_); + emit RequestTextureToBuffer(tex_in, buffer); } diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h index 336fc4183..056441b0c 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/backend/opengl/openglworker.h @@ -12,65 +12,23 @@ class OpenGLWorker : public VideoRenderWorker { Q_OBJECT public: - OpenGLWorker(QOpenGLContext* share_ctx, - OpenGLShaderCache* shader_cache, - OpenGLTextureCache* texture_cache, - VideoRenderFrameCache* frame_cache, + OpenGLWorker(VideoRenderFrameCache* frame_cache, QObject* parent = nullptr); - virtual ~OpenGLWorker() override; +signals: + void RequestFrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable* table); + + void RequestRunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable* output_params); + + void RequestTextureToBuffer(const QVariant& texture, QByteArray& buffer); protected: - /** - * @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). - */ - virtual bool InitInternal() override; - - virtual void CloseInternal() override; - virtual void FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable* table) override; virtual void RunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable* output_params) override; virtual void TextureToBuffer(const QVariant& texture, QByteArray& buffer) override; - virtual void ParametersChangedEvent() override; - -private: - QOpenGLContext* share_ctx_; - - QOpenGLContext* ctx_; - QOffscreenSurface surface_; - - QOpenGLFunctions* functions_; - - OpenGLFramebuffer buffer_; - - OpenGLShaderCache* shader_cache_; - - OpenGLTextureCache* texture_cache_; - -private slots: - void FinishInit(); - }; #endif // OPENGLPROCESSOR_H diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 1f56556d5..29fd55ec7 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -294,7 +294,8 @@ TimeRange VideoRenderBackend::PopNextFrameFromQueue() void VideoRenderBackend::ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value) { if (!only_signal_last_frame_requested_ || last_time_requested_ == path.in() || frame_cache_.TimeToHash(last_time_requested_) == hash) { - EmitCachedFrameReady(path.in(), value, job_time); + Q_UNUSED(job_time) + //EmitCachedFrameReady(path.in(), value, job_time); } if (!(operating_mode_ & VideoRenderWorker::kDownloadOnly)) {