Files
oak-editor/app/render/backend/opengl/openglbackend.cpp
T
itsmattkc f6064b40df hold worker busy state in renderbackend rather than in renderworker
Workers run in different threads and the backends can poll whether the worker
is currently busy or not. However the previous iteration has the worker (and an
atomic int) provide the busy state which could easily desync with the main
thread (since all workers run in different threads). By holding the busy states
in the main thread, the main thread will always be able to poll the busy state
accurately.
2019-12-06 19:38:57 +11:00

199 lines
5.4 KiB
C++

#include "openglbackend.h"
#include <QEventLoop>
#include <QThread>
#include "functions.h"
OpenGLBackend::OpenGLBackend(QObject *parent) :
VideoRenderBackend(parent),
last_download_thread_(0)
{
}
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_, 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());
return true;
}
void OpenGLBackend::CloseInternal()
{
Decompile();
master_texture_ = nullptr;
}
OpenGLTexturePtr OpenGLBackend::GetCachedFrameAsTexture(const rational &time)
{
const char* cached_frame = GetCachedFrame(time);
if (cached_frame != nullptr) {
master_texture_->Upload(cached_frame);
return master_texture_;
}
return nullptr;
}
bool OpenGLBackend::CompileInternal()
{
if (viewer_node() == nullptr || !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
QString node_code = n->Code();
// If the node has no code, it mustn't be GPU accelerated
if (node_code.isEmpty()) {
// 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;
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, node_code)) {
SetError(QStringLiteral("Failed to add OpenGL fragment shader code"));
return false;
}
if (!program->addShaderFromSourceCode(QOpenGLShader::Vertex, OpenGLShader::CodeDefaultVertex())) {
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);
//qDebug() << "Compiled" << connected_output->parent()->id() << "->" << connected_output->id();
}
}
}
return true;
}
void OpenGLBackend::DecompileInternal()
{
shader_cache_.Clear();
}
bool OpenGLBackend::TimeIsCached(const TimeRange &time)
{
return cache_queue_.contains(time);
}
void OpenGLBackend::ThreadCompletedFrame(NodeDependency path, QByteArray hash, NodeValueTable table)
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
QVariant value = table.Get(NodeParam::kTexture);
OpenGLTexturePtr texture = value.value<OpenGLTexturePtr>();
if (!texture) {
// No frame received, we set hash to an empty
frame_cache()->RemoveHash(path.in(), hash);
} else {
// Received a texture, let's download it
QString cache_fn = frame_cache()->CachePathName(hash);
// Find an available worker to download this texture
QMetaObject::invokeMethod(processors_.at(last_download_thread_%processors_.size()),
"Download",
Q_ARG(NodeDependency, path),
Q_ARG(QByteArray, hash),
Q_ARG(QVariant, QVariant::fromValue(texture)),
Q_ARG(QString, cache_fn));
last_download_thread_++;
}
// Set as push texture
if (!TimeIsCached(TimeRange(path.in(), path.in()))) {
emit CachedFrameReady(path.in(), value);
}
// Queue up a new frame for this worker
CacheNext();
}
void OpenGLBackend::ThreadCompletedDownload(NodeDependency dep, QByteArray hash)
{
frame_cache()->SetHash(dep.in(), hash);
// Emit for each frame that has this hash (some may have been added in ThreadSkippedFrame)
QList<rational> times_with_this_hash = frame_cache()->TimesWithHash(hash);
foreach (const rational& t, times_with_this_hash) {
emit CachedTimeReady(t);
}
}
void OpenGLBackend::ThreadSkippedFrame(NodeDependency dep, QByteArray hash)
{
frame_cache()->SetHash(dep.in(), hash);
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
// Queue up a new frame for this worker
CacheNext();
}
void OpenGLBackend::ThreadHashAlreadyExists(NodeDependency dep, QByteArray hash)
{
ThreadCompletedDownload(dep, hash);
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
// Queue up a new frame for this worker
CacheNext();
}