Files
oak-editor/app/render/backend/opengl/openglbackend.cpp
T
itsmattkc 24f7eeb210 renderer: implemented function to wait for workers in the main thread
The workers run in separate threads meaning if any significant change is made
(e.g. parameters changing, or even closing the program), these workers may still
be mid-render. This is particularly problematic when closing since the nodes a
worker is rendering may be deleted mid-render. Render backends now have a
function that pauses the main thread (but starts a second event loop so the UI
isn't frozen) until the worker threads are all finished. This way, massive
changes can be made safely without race conditions.
2020-01-22 17:11:17 +11:00

197 lines
5.1 KiB
C++

#include "openglbackend.h"
#include <QEventLoop>
#include <QThread>
#include "openglrenderfunctions.h"
OpenGLBackend::OpenGLBackend(QObject *parent) :
VideoRenderBackend(parent),
master_texture_(nullptr)
{
}
OpenGLBackend::~OpenGLBackend()
{
Close();
}
bool OpenGLBackend::InitInternal()
{
if (!VideoRenderBackend::InitInternal()) {
return false;
}
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++) {
// Create one processor object for each thread
OpenGLWorker* processor = new OpenGLWorker(share_ctx, &shader_cache_, &texture_cache_, frame_cache());
processor->SetParameters(params());
processors_.append(processor);
}
// 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());
// Create copy buffer/pipeline
copy_buffer_.Create(share_ctx);
copy_pipeline_ = OpenGLShader::CreateDefault();
return true;
}
void OpenGLBackend::CloseInternal()
{
copy_buffer_.Destroy();
copy_pipeline_ = nullptr;
master_texture_ = nullptr;
}
OpenGLTexturePtr OpenGLBackend::GetCachedFrameAsTexture(const rational &time)
{
const char* cached_frame = GetCachedFrame(time);
if (cached_frame) {
master_texture_->Upload(cached_frame);
return master_texture_;
}
return nullptr;
}
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<Node*> 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<OpenGLShader>())) {
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)
{
OpenGLTextureCache::ReferencePtr ref = value.value<OpenGLTextureCache::ReferencePtr>();
OpenGLTexturePtr tex;
if (ref && ref->texture()) {
tex = CopyTexture(ref->texture());
} else {
tex = nullptr;
}
emit CachedFrameReady(time, QVariant::fromValue(tex), job_time);
}
void OpenGLBackend::ParamsChangedEvent()
{
// If we're initiated, we need to recreate the texture. Otherwise this backend isn't active so it doesn't matter.
if (IsInitiated()) {
master_texture_->Destroy();
master_texture_->Create(QOpenGLContext::currentContext(),
params().effective_width(),
params().effective_height(),
params().format());
}
}
OpenGLTexturePtr OpenGLBackend::CopyTexture(OpenGLTexturePtr input)
{
input->Lock();
QOpenGLContext* ctx = QOpenGLContext::currentContext();
OpenGLTexturePtr copy = std::make_shared<OpenGLTexture>();
copy->Create(ctx, input->width(), input->height(), input->format());
ctx->functions()->glViewport(0, 0, input->width(), input->height());
copy_buffer_.Attach(copy);
copy_buffer_.Bind();
input->Bind();
OpenGLRenderFunctions::Blit(copy_pipeline_);
input->Release();
copy_buffer_.Release();
copy_buffer_.Detach();
input->Unlock();
return copy;
}