separation of previous renderer into base and derivation complete

The previous iteration was fairly OpenGL-heavy. It's now been separated into
a base class that is OpenGL independent and a derived class that is
OpenGL-based. Over time this should allow for portability away from OpenGL
if necessary.
This commit is contained in:
itsmattkc
2019-11-01 12:58:38 +11:00
parent f13fb648c7
commit 429514b1fd
18 changed files with 267 additions and 777 deletions
+1 -1
View File
@@ -317,7 +317,7 @@ void Core::DeclareTypesForQt()
qRegisterMetaType<Task::Status>("Task::Status");
qRegisterMetaType<NodeDependency>();
qRegisterMetaType<rational>();
qRegisterMetaType<RenderTexturePtr>();
qRegisterMetaType<OpenGLTexturePtr>();
}
void Core::StartGUI(bool full_screen)
-5
View File
@@ -112,11 +112,6 @@ void ViewerPanel::SetTime(const int64_t &timestamp)
viewer_->SetTime(timestamp);
}
void ViewerPanel::SetTexture(RenderTexturePtr tex)
{
viewer_->SetTexture(tex);
}
void ViewerPanel::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
-9
View File
@@ -63,15 +63,6 @@ public:
rational GetTime();
public slots:
/**
* @brief Set the texture to draw and draw it
*
* Wrapper function for Viewer::SetTexture().
*
* @param tex
*/
void SetTexture(RenderTexturePtr tex);
void SetTime(const int64_t& timestamp);
protected:
-7
View File
@@ -25,12 +25,5 @@ set(OLIVE_SOURCES
render/backend/audiorenderbackend.cpp
render/backend/videorenderbackend.h
render/backend/videorenderbackend.cpp
# FIXME: Remove these
render/backend/videorendererthreadbase.h
render/backend/videorendererthreadbase.cpp
render/backend/renderinstance.h
render/backend/renderinstance.cpp
PARENT_SCOPE
)
+160 -12
View File
@@ -2,8 +2,12 @@
#include <QThread>
OpenGLBackend::OpenGLBackend(QOpenGLContext *share_ctx) :
share_ctx_(share_ctx)
#include "functions.h"
OpenGLBackend::OpenGLBackend(QOpenGLContext *share_ctx, QObject *parent) :
VideoRenderBackend(parent),
share_ctx_(share_ctx),
push_time_(-1)
{
}
@@ -14,7 +18,9 @@ OpenGLBackend::~OpenGLBackend()
bool OpenGLBackend::Init()
{
threads_.resize(QThread::idealThreadCount());
if (!OpenGLBackend::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
@@ -22,16 +28,14 @@ bool OpenGLBackend::Init()
share_ctx_->doneCurrent();
// Initiate one thread per CPU core
for (int i=0;i<threads_.size();i++) {
// Instantiate thread
QThread* thread = new QThread(this);
threads_.replace(i, thread);
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
processor->SetParameters(VideoRenderingParams(viewer_node()->video_params(), olive::PIX_FMT_RGBA16F, olive::kOffline));
processor->SetParameters(params());
// Finally, we can move it to its own thread
processor->moveToThread(thread);
@@ -40,23 +44,64 @@ bool OpenGLBackend::Init()
// We've finished creating shared contexts, we can now restore the context to its previous current state
share_ctx_->makeCurrent(old_surface);
// 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 internal FBO for copying textures
copy_buffer_.Create(share_ctx_);
copy_buffer_.Attach(master_texture_);
copy_pipeline_ = OpenGLShader::CreateDefault();
return true;
}
void OpenGLBackend::GenerateFrame(const rational &time)
{
Q_UNUSED(time)
/*threads().first()->Queue(NodeDependency(viewer_node()->texture_input()->get_connected_output(), time, time),
true,
false);*/
}
void OpenGLBackend::Close()
{
if (!IsStarted()) {
return;
}
Decompile();
// Clear all cores
for (int i=0;i<threads_.size();i++) {
delete threads_.at(i);
copy_buffer_.Destroy();
master_texture_ = nullptr;
copy_pipeline_ = nullptr;
OpenGLBackend::Close();
}
OpenGLTexturePtr OpenGLBackend::GetCachedFrameAsTexture(const rational &time)
{
last_time_requested_ = time;
if (push_time_ >= 0) {
rational temp_push_time = push_time_;
push_time_ = -1;
if (time == temp_push_time) {
return master_texture_;
}
}
threads_.clear();
const char* cached_frame = GetCachedFrame(time);
if (cached_frame != nullptr) {
master_texture_->Upload(cached_frame);
return master_texture_;
}
return nullptr;
}
bool OpenGLBackend::Compile()
@@ -160,6 +205,109 @@ QString OpenGLBackend::GenerateShaderID(NodeOutput *output)
return QString("%1:%2").arg(output->parent()->id(), output->id());
}
void OpenGLBackend::ThreadCallback(OpenGLTexturePtr texture, const rational& time, const QByteArray& hash)
{
// Threads are all done now, time to proceed
caching_ = false;
DeferMap(time, hash);
if (texture != nullptr) {
// We received a texture, time to start downloading it
QString fn = CachePathName(hash);
/*
download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture,
fn,
hash);
last_download_thread_++;
*/
} else {
// There was no texture here, we must update the viewer
DownloadThreadComplete(hash);
}
// If the connected output is using this time, signal it to update
if (last_time_requested_ == time) {
copy_buffer_.Bind();
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
if (texture == nullptr) {
// No texture, clear the master and push it
f->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
f->glClear(GL_COLOR_BUFFER_BIT);
} else {
texture->Bind();
f->glViewport(0, 0, master_texture_->width(), master_texture_->height());
olive::gl::Blit(copy_pipeline_);
texture->Release();
}
copy_buffer_.Release();
push_time_ = time;
emit CachedFrameReady(time);
}
CacheNext();
}
void OpenGLBackend::ThreadRequestSibling(NodeDependency dep)
{
Q_UNUSED(dep)
// Try to queue another thread to run this dep in advance
for (int i=1;i<threads().size();i++) {
/*if (threads().at(i)->Queue(dep, false, true)) {
return;
}*/
}
}
void OpenGLBackend::ThreadSkippedFrame(const rational& time, const QByteArray& hash)
{
caching_ = false;
DeferMap(time, hash);
if (!IsCaching(hash)) {
DownloadThreadComplete(hash);
// Signal output to update value
emit CachedFrameReady(time);
}
CacheNext();
}
void OpenGLBackend::DownloadThreadComplete(const QByteArray &hash)
{
cache_hash_list_mutex_.lock();
cache_hash_list_.removeAll(hash);
cache_hash_list_mutex_.unlock();
for (int i=0;i<deferred_maps_.size();i++) {
const HashTimeMapping& deferred = deferred_maps_.at(i);
if (deferred_maps_.at(i).hash == hash) {
// Insert into hash map
time_hash_map_.insert(deferred.time, deferred.hash);
deferred_maps_.removeAt(i);
i--;
}
}
}
OpenGLProcessor::OpenGLProcessor(QOpenGLContext *share_ctx, QObject *parent) :
QObject(parent),
share_ctx_(share_ctx),
+25 -6
View File
@@ -5,8 +5,10 @@
#include <QOffscreenSurface>
#include <QOpenGLShaderProgram>
#include "../renderbackend.h"
#include "../videorenderbackend.h"
#include "openglframebuffer.h"
#include "opengltexture.h"
#include "openglshader.h"
class OpenGLProcessor : public QObject {
public:
@@ -45,24 +47,27 @@ private:
VideoRenderingParams video_params_;
};
class OpenGLBackend : public RenderBackend
class OpenGLBackend : public VideoRenderBackend
{
public:
OpenGLBackend(QOpenGLContext* share_ctx);
OpenGLBackend(QOpenGLContext* share_ctx, QObject* parent = nullptr);
virtual ~OpenGLBackend() override;
virtual bool Init() override;
virtual void GenerateFrame(const rational& time) override;
virtual void Close() override;
OpenGLTexturePtr GetCachedFrameAsTexture(const rational& time);
public slots:
virtual bool Compile() override;
virtual void Decompile() override;
protected:
virtual void GenerateFrame(const rational& time) override;
private:
QOpenGLContext* share_ctx_;
@@ -79,8 +84,22 @@ private:
QList<CompiledNode> compiled_nodes_;
QVector<QThread*> threads_;
QVector<OpenGLProcessor*> processors_;
OpenGLTexturePtr master_texture_;
rational push_time_;
OpenGLFramebuffer copy_buffer_;
OpenGLShaderPtr copy_pipeline_;
private slots:
void ThreadCallback(OpenGLTexturePtr texture, const rational& time, const QByteArray& hash);
void ThreadRequestSibling(NodeDependency dep);
void ThreadSkippedFrame(const rational &time, const QByteArray &hash);
void DownloadThreadComplete(const QByteArray &hash);
};
#endif // OPENGLBACKEND_H
@@ -88,7 +88,7 @@ void OpenGLFramebuffer::Release()
context_->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
void OpenGLFramebuffer::Attach(RenderTexturePtr texture)
void OpenGLFramebuffer::Attach(OpenGLTexturePtr texture)
{
if (context_ == nullptr) {
return;
@@ -98,7 +98,7 @@ void OpenGLFramebuffer::Attach(RenderTexturePtr texture)
AttachInternal(texture_->texture(), false);
}
void OpenGLFramebuffer::AttachBackBuffer(RenderTexturePtr texture)
void OpenGLFramebuffer::AttachBackBuffer(OpenGLTexturePtr texture)
{
if (context_ == nullptr) {
return;
@@ -42,9 +42,9 @@ public:
void Release();
void Attach(RenderTexturePtr texture);
void Attach(OpenGLTexturePtr texture);
void AttachBackBuffer(RenderTexturePtr texture);
void AttachBackBuffer(OpenGLTexturePtr texture);
void Detach();
@@ -60,7 +60,7 @@ private:
GLuint buffer_;
RenderTexturePtr texture_;
OpenGLTexturePtr texture_;
};
#endif // OPENGLFRAMEBUFFER_H
+2 -2
View File
@@ -84,7 +84,7 @@ private:
olive::PixelFormat format_;
};
using RenderTexturePtr = std::shared_ptr<OpenGLTexture>;
Q_DECLARE_METATYPE(RenderTexturePtr)
using OpenGLTexturePtr = std::shared_ptr<OpenGLTexture>;
Q_DECLARE_METATYPE(OpenGLTexturePtr)
#endif // OPENGLTEXTURE_H
-2
View File
@@ -14,8 +14,6 @@ public:
virtual bool Init() = 0;
virtual void GenerateFrame(const rational& time) = 0;
virtual void Close() = 0;
const QString& GetError() const;
-125
View File
@@ -1,125 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "renderinstance.h"
#include <QDebug>
#include "opengl/openglshader.h"
RenderInstance::RenderInstance(const VideoRenderingParams& params) :
share_ctx_(nullptr),
params_(params)
{
// Create offscreen surface
surface_.create();
}
RenderInstance::~RenderInstance()
{
// Destroy offscreen surface
surface_.destroy();
}
void RenderInstance::SetShareContext(QOpenGLContext *share)
{
Q_ASSERT(!IsStarted());
share_ctx_ = share;
}
bool RenderInstance::Start()
{
if (IsStarted()) {
return true;
}
// Create context object
ctx_ = new QOpenGLContext();
// If we're sharing resources, set this up now
if (share_ctx_ != nullptr) {
ctx_->setShareContext(share_ctx_);
}
// Create OpenGL context (automatically destroys any existing if there is one)
if (!ctx_->create()) {
qWarning() << QStringLiteral("Failed to create OpenGL context in thread %1").arg(reinterpret_cast<quintptr>(this));
return false;
}
// Make context current on that surface
if (!ctx_->makeCurrent(&surface_)) {
qWarning() << QStringLiteral("Failed to makeCurrent() on offscreen surface in thread %1").arg(reinterpret_cast<quintptr>(this));
return false;
}
buffer_.Create(ctx_);
// Set viewport to the compositing dimensions
ctx_->functions()->glViewport(0, 0, params_.effective_width(), params_.effective_height());
ctx_->functions()->glEnable(GL_BLEND);
// Set up default pipeline
default_pipeline_ = OpenGLShader::CreateDefault();
return true;
}
void RenderInstance::Stop()
{
if (IsStarted()) {
return;
}
// Destroy pipeline
default_pipeline_ = nullptr;
// Destroy buffer
buffer_.Destroy();
// Destroy context
delete ctx_;
}
bool RenderInstance::IsStarted()
{
return buffer_.IsCreated();
}
OpenGLFramebuffer *RenderInstance::buffer()
{
return &buffer_;
}
QOpenGLContext *RenderInstance::context()
{
return ctx_;
}
const VideoRenderingParams &RenderInstance::params() const
{
return params_;
}
OpenGLShaderPtr RenderInstance::default_pipeline() const
{
return default_pipeline_;
}
-80
View File
@@ -1,80 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef GLINSTANCE_H
#define GLINSTANCE_H
#include <QMatrix4x4>
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include "opengl/openglshader.h"
#include "opengl/openglframebuffer.h"
#include "render/rendermodes.h"
#include "render/videoparams.h"
/**
* @brief An object containing all resources necessary for each thread to support hardware accelerated rendering
*
* RenderInstance contains everything that Nodes will need to draw with on a per-thread basis.
*
* Due to its usage of QOffscreenSurface, a RenderInstance instance must be constructed in the main (GUI) thread. From
* there it is safe to call Start() on in another thread.
*/
class RenderInstance : public QObject
{
public:
RenderInstance(const VideoRenderingParams &params);
virtual ~RenderInstance() override;
Q_DISABLE_COPY_MOVE(RenderInstance)
void SetShareContext(QOpenGLContext* share);
bool Start();
void Stop();
bool IsStarted();
OpenGLFramebuffer* buffer();
QOpenGLContext* context();
const VideoRenderingParams& params() const;
OpenGLShaderPtr default_pipeline() const;
private:
QOpenGLContext* ctx_;
QOpenGLContext* share_ctx_;
QOffscreenSurface surface_;
OpenGLFramebuffer buffer_;
VideoRenderingParams params_;
OpenGLShaderPtr default_pipeline_;
};
#endif // GLINSTANCE_H
+27 -185
View File
@@ -27,6 +27,7 @@
#include <QDebug>
#include <QDir>
#include <QtMath>
#include <QThread>
#include "common/filefunctions.h"
#include "opengl/functions.h"
@@ -34,10 +35,8 @@
VideoRenderBackend::VideoRenderBackend(QObject *parent) :
RenderBackend(parent),
started_(false),
caching_(false),
push_time_(-1),
starting_(false)
started_(false)
{
// FIXME: Cache name should actually be the name of the sequence
SetCacheName("Test");
@@ -132,6 +131,16 @@ void VideoRenderBackend::ViewerNodeChangedEvent(ViewerOutput *node)
}
}
const QVector<QThread *> &VideoRenderBackend::threads()
{
return threads_;
}
const VideoRenderingParams &VideoRenderBackend::params() const
{
return params_;
}
void VideoRenderBackend::SetParameters(const VideoRenderingParams& params)
{
// Since we're changing parameters, all the existing threads are invalid and must be removed. They will start again
@@ -151,69 +160,16 @@ bool VideoRenderBackend::Init()
return true;
}
QOpenGLContext* ctx = QOpenGLContext::currentContext();
int background_thread_count = QThread::idealThreadCount();
// Some OpenGL implementations (notably wgl) require the context not to be current before sharing
QSurface* old_surface = ctx->surface();
ctx->doneCurrent();
threads_.resize(background_thread_count);
threads_.resize(QThread::idealThreadCount());
for (int i=0;i<threads_.size();i++) {
threads_[i] = std::make_shared<RendererProcessThread>(this, ctx, params_);
threads_[i]->StartThread(QThread::LowPriority);
QThread* thread = new QThread(this);
threads_.replace(i, thread);
// Ensure this connection is "Queued" so that it always runs in this object's threaded rather than any of the
// other threads
connect(threads_.at(i).get(),
SIGNAL(RequestSibling(NodeDependency)),
this,
SLOT(ThreadRequestSibling(NodeDependency)),
Qt::QueuedConnection);
// We use low priority to keep the app responsive at all times (GUI thread should always prioritize over this one)
thread->start(QThread::LowPriority);
}
// Connect first thread (master thread) to the callback
connect(threads_.first().get(),
SIGNAL(CachedFrame(RenderTexturePtr, const rational&, const QByteArray&)),
this,
SLOT(ThreadCallback(RenderTexturePtr, const rational&, const QByteArray&)),
Qt::QueuedConnection);
connect(threads_.first().get(),
SIGNAL(FrameSkipped(const rational&, const QByteArray&)),
this,
SLOT(ThreadSkippedFrame(const rational&, const QByteArray&)),
Qt::QueuedConnection);
download_threads_.resize(background_thread_count);
for (int i=0;i<download_threads_.size();i++) {
// Create download thread
download_threads_[i] = std::make_shared<VideoRendererDownloadThread>(ctx, params_);
download_threads_[i]->StartThread(QThread::LowPriority);
connect(download_threads_[i].get(),
SIGNAL(Downloaded(const QByteArray&)),
this,
SLOT(DownloadThreadComplete(const QByteArray&)),
Qt::QueuedConnection);
}
last_download_thread_ = 0;
// Restore context now that thread creation is complete
ctx->makeCurrent(old_surface);
// Create master texture (the one sent to the viewer)
master_texture_ = std::make_shared<OpenGLTexture>();
master_texture_->Create(ctx, params_.effective_width(), params_.effective_height(), params_.format());
// Create internal FBO for copying textures
copy_buffer_.Create(ctx);
copy_buffer_.Attach(master_texture_);
copy_pipeline_ = OpenGLShader::CreateDefault();
cache_frame_load_buffer_.resize(PixelService::GetBufferSize(params_.format(), params_.effective_width(), params_.effective_height()));
started_ = true;
@@ -229,20 +185,11 @@ void VideoRenderBackend::Close()
started_ = false;
foreach (RendererDownloadThreadPtr download_thread_, download_threads_) {
download_thread_->Cancel();
}
download_threads_.clear();
foreach (RendererProcessThreadPtr process_thread, threads_) {
process_thread->Cancel();
foreach (QThread* thread, threads_) {
thread->quit();
}
threads_.clear();
copy_buffer_.Destroy();
master_texture_ = nullptr;
copy_pipeline_ = nullptr;
cache_frame_load_buffer_.clear();
}
@@ -278,7 +225,7 @@ void VideoRenderBackend::CacheNext()
qDebug() << "Caching" << cache_frame.toDouble();
threads_.first()->Queue(NodeDependency(viewer_node()->texture_input()->get_connected_output(), cache_frame, cache_frame), true, false);
GenerateFrame(cache_frame);
caching_ = true;
}
@@ -329,118 +276,10 @@ bool VideoRenderBackend::TryCache(const QByteArray &hash)
return !is_caching;
}
void VideoRenderBackend::ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash)
{
// Threads are all done now, time to proceed
caching_ = false;
DeferMap(time, hash);
if (texture != nullptr) {
// We received a texture, time to start downloading it
QString fn = CachePathName(hash);
download_threads_[last_download_thread_%download_threads_.size()]->Queue(texture,
fn,
hash);
last_download_thread_++;
} else {
// There was no texture here, we must update the viewer
DownloadThreadComplete(hash);
}
// If the connected output is using this time, signal it to update
if (last_time_requested_ == time) {
copy_buffer_.Bind();
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
if (texture == nullptr) {
// No texture, clear the master and push it
f->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
f->glClear(GL_COLOR_BUFFER_BIT);
} else {
texture->Bind();
f->glViewport(0, 0, master_texture_->width(), master_texture_->height());
olive::gl::Blit(copy_pipeline_);
texture->Release();
}
copy_buffer_.Release();
push_time_ = time;
emit CachedFrameReady(time);
}
CacheNext();
}
void VideoRenderBackend::ThreadRequestSibling(NodeDependency dep)
{
// Try to queue another thread to run this dep in advance
for (int i=1;i<threads_.size();i++) {
if (threads_.at(i)->Queue(dep, false, true)) {
return;
}
}
}
void VideoRenderBackend::ThreadSkippedFrame(const rational& time, const QByteArray& hash)
{
caching_ = false;
DeferMap(time, hash);
if (!IsCaching(hash)) {
DownloadThreadComplete(hash);
// Signal output to update value
emit CachedFrameReady(time);
}
CacheNext();
}
void VideoRenderBackend::DownloadThreadComplete(const QByteArray &hash)
{
cache_hash_list_mutex_.lock();
cache_hash_list_.removeAll(hash);
cache_hash_list_mutex_.unlock();
for (int i=0;i<deferred_maps_.size();i++) {
const HashTimeMapping& deferred = deferred_maps_.at(i);
if (deferred_maps_.at(i).hash == hash) {
// Insert into hash map
time_hash_map_.insert(deferred.time, deferred.hash);
deferred_maps_.removeAt(i);
i--;
}
}
}
RenderTexturePtr VideoRenderBackend::GetCachedFrame(const rational &time)
const char *VideoRenderBackend::GetCachedFrame(const rational &time)
{
last_time_requested_ = time;
if (push_time_ >= 0) {
rational temp_push_time = push_time_;
push_time_ = -1;
if (time == temp_push_time) {
return master_texture_;
}
}
if (viewer_node() == nullptr) {
// Nothing is connected - nothing to show or render
return nullptr;
@@ -468,9 +307,7 @@ RenderTexturePtr VideoRenderBackend::GetCachedFrame(const rational &time)
in->close();
master_texture_->Upload(cache_frame_load_buffer_.data());
return master_texture_;
return cache_frame_load_buffer_.constData();
} else {
qWarning() << "OIIO Error:" << OIIO::geterror().c_str();
}
@@ -479,3 +316,8 @@ RenderTexturePtr VideoRenderBackend::GetCachedFrame(const rational &time)
return nullptr;
}
bool VideoRenderBackend::IsStarted()
{
return started_;
}
+40 -59
View File
@@ -22,17 +22,11 @@
#define VIDEORENDERERBACKEND_H
#include <QLinkedList>
#include <QOpenGLTexture>
#include "node/output/viewer/viewer.h"
#include "renderbackend.h"
#include "render/pixelformat.h"
#include "render/rendermodes.h"
#include "opengl/openglframebuffer.h"
#include "opengl/openglshader.h"
#include "opengl/opengltexture.h"
#include "videorendererdownloadthread.h"
#include "videorendererprocessthread.h"
/**
* @brief A multithreaded OpenGL based renderer for node systems
@@ -47,7 +41,7 @@ public:
* Constructing a Renderer object will not start any threads/backend on its own. Use Start() to do this and Stop()
* when the Renderer is about to be destroyed.
*/
VideoRenderBackend(QObject* parent);
VideoRenderBackend(QObject* parent = nullptr);
virtual ~VideoRenderBackend() override;
@@ -98,42 +92,22 @@ public:
*/
bool TryCache(const QByteArray& hash);
RenderTexturePtr GetCachedFrame(const rational& time);
virtual void GenerateFrame(const rational&) override {}
bool IsStarted();
public slots:
virtual void InvalidateCache(const rational &start_range, const rational &end_range) override;
virtual bool Compile() override {return true;}
virtual void Decompile() override {}
protected:
virtual void ViewerNodeChangedEvent(ViewerOutput* node) override;
signals:
void CachedFrameReady(const rational& time);
private:
struct HashTimeMapping {
rational time;
QByteArray hash;
};
/**
* @brief Internal function for generating the cache ID
*/
void GenerateCacheIDInternal();
virtual void ViewerNodeChangedEvent(ViewerOutput* node) override;
/**
* @brief Function called when there are frames in the queue to cache
*
* This function is NOT thread-safe and should only be called in the main thread.
*/
void CacheNext();
virtual void GenerateFrame(const rational&) = 0;
bool ShouldPushTexture(const rational &time);
const char *GetCachedFrame(const rational& time);
/**
* @brief Return the path of the cached image at this time
@@ -142,10 +116,41 @@ private:
void DeferMap(const rational &time, const QByteArray &hash);
/**
* @brief Function called when there are frames in the queue to cache
*
* This function is NOT thread-safe and should only be called in the main thread.
*/
void CacheNext();
const QVector<QThread*>& threads();
const VideoRenderingParams& params() const;
QMap<rational, QByteArray> time_hash_map_;
QList<HashTimeMapping> deferred_maps_;
QMutex cache_hash_list_mutex_;
QVector<QByteArray> cache_hash_list_;
rational last_time_requested_;
bool caching_;
signals:
void CachedFrameReady(const rational& time);
private:
/**
* @brief Internal function for generating the cache ID
*/
void GenerateCacheIDInternal();
/**
* @brief Internal list of RenderProcessThreads
*/
QVector<RendererProcessThreadPtr> threads_;
QVector<QThread*> threads_;
/**
* @brief Internal variable that contains whether the Renderer has started or not
@@ -154,42 +159,18 @@ private:
VideoRenderingParams params_;
rational last_time_requested_;
QLinkedList<rational> cache_queue_;
QString cache_name_;
qint64 cache_time_;
QString cache_id_;
bool caching_;
QVector<uchar*> cache_frame_load_buffer_;
QByteArray cache_frame_load_buffer_;
QVector<RendererDownloadThreadPtr> download_threads_;
int last_download_thread_;
RenderTexturePtr master_texture_;
rational push_time_;
OpenGLFramebuffer copy_buffer_;
OpenGLShaderPtr copy_pipeline_;
QMap<rational, QByteArray> time_hash_map_;
QMutex cache_hash_list_mutex_;
QVector<QByteArray> cache_hash_list_;
QList<HashTimeMapping> deferred_maps_;
bool starting_;
/*QVector<RendererDownloadThreadPtr> download_threads_;
int last_download_thread_;*/
private slots:
void ThreadCallback(RenderTexturePtr texture, const rational& time, const QByteArray& hash);
void ThreadRequestSibling(NodeDependency dep);
void ThreadSkippedFrame(const rational &time, const QByteArray &hash);
void DownloadThreadComplete(const QByteArray &hash);
};
@@ -1,188 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "videorendererthreadbase.h"
#include <QDebug>
#include <QThread>
#include "common/define.h"
#include "videorenderbackend.h"
VideoRendererThreadBase::VideoRendererThreadBase(VideoRenderBackend* parent, QOpenGLContext *share_ctx, const VideoRenderingParams &params) :
parent_(parent),
share_ctx_(share_ctx),
render_instance_(params)
{
connect(share_ctx_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Cancel()));
}
RenderInstance *VideoRendererThreadBase::render_instance()
{
return &render_instance_;
}
void VideoRendererThreadBase::Start()
{
render_instance_.SetShareContext(share_ctx_);
// Allocate and create resources
render_instance_.Start();
// Set up download functions
f = render_instance()->context()->functions();
xf = render_instance()->context()->extraFunctions();
f->glGenFramebuffers(1, &read_buffer_);
int buffer_size = PixelService::GetBufferSize(render_instance()->params().format(),
render_instance()->params().width(),
render_instance()->params().height());
data_buffer_.resize(buffer_size);
format_info_ = PixelService::GetPixelFormatInfo(render_instance()->params().format());
// Set up OIIO::ImageSpec for compressing cached images on disk
spec_ = OIIO::ImageSpec(render_instance()->params().width(), render_instance()->params().height(), kRGBAChannels, format_info_.oiio_desc);
spec_.attribute("compression", "dwaa:200");
}
void VideoRendererThreadBase::Stop()
{
f->glDeleteFramebuffers(1, &read_buffer_);
// Free all resources
render_instance_.Stop();
thread()->quit();
}
void VideoRendererThreadBase::Process(const NodeDependency &path, bool sibling)
{
// Process the Node
NodeOutput* output_to_process = path.node();
Node* node_to_process = output_to_process->parent();
texture_ = nullptr;
QList<Node*> all_deps;
bool has_hash = false;
bool can_cache = true;
QByteArray hash;
if (!sibling) {
node_to_process->Lock();
all_deps = node_to_process->GetDependencies();
foreach (Node* dep, all_deps) {
dep->Lock();
}
// Check hash
QCryptographicHash hasher(QCryptographicHash::Sha1);
node_to_process->Hash(&hasher, output_to_process, path.in());
hash = hasher.result();
has_hash = parent_->HasHash(hash);
can_cache = false;
}
if (!has_hash){
if ((can_cache = parent_->TryCache(hash))) {
QList<NodeDependency> deps = node_to_process->RunDependencies(output_to_process, path.in());
// Ask for other threads to run these deps while we're here
if (!deps.isEmpty()) {
for (int i=1;i<deps.size();i++) {
emit RequestSibling(deps.at(i));
}
}
// Get the requested value
texture_ = output_to_process->get_value(path.in(), path.in()).value<RenderTexturePtr>();
render_instance()->context()->functions()->glFinish();
}
}
if (!sibling) {
foreach (Node* dep, all_deps) {
dep->Unlock();
}
node_to_process->Unlock();
}
if (can_cache) {
// We cached this frame, signal that it will need to be downloaded to disk
emit CachedFrame(texture_, path.in(), hash);
} else {
// This hash already exists, no need to cache, just map it
emit FrameSkipped(path.in(), hash);
}
}
void VideoRendererThreadBase::Download(RenderTexturePtr texture, const QString &fn, const QByteArray &hash)
{
// Download the texture
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, read_buffer_);
xf->glFramebufferTexture2D(GL_READ_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
texture->texture(),
0);
f->glReadPixels(0,
0,
texture->width(),
texture->height(),
format_info_.pixel_format,
format_info_.gl_pixel_type,
data_buffer_.data());
xf->glFramebufferTexture2D(GL_READ_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
0,
0);
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
std::string working_fn_std = fn.toStdString();
std::unique_ptr<OIIO::ImageOutput> out = OIIO::ImageOutput::create(working_fn_std);
if (out) {
out->open(working_fn_std, spec_);
out->write_image(format_info_.oiio_desc, data_buffer_.data());
out->close();
emit Downloaded(hash);
} else {
qWarning() << QStringLiteral("Failed to open output file \"%1\"").arg(fn);
}
}
@@ -1,85 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef RENDERTHREAD_H
#define RENDERTHREAD_H
#include <memory>
#include <OpenImageIO/imageio.h>
#include "node/node.h"
#include "render/videoparams.h"
#include "renderinstance.h"
#include "render/pixelservice.h"
class VideoRenderBackend;
class VideoRendererThreadBase : public QObject
{
Q_OBJECT
public:
VideoRendererThreadBase(VideoRenderBackend* parent, QOpenGLContext* share_ctx, const VideoRenderingParams& params);
RenderInstance* render_instance();
public slots:
void Start();
void Stop();
void Process(const NodeDependency &dep, bool sibling);
void Download(RenderTexturePtr texture, const QString &fn, const QByteArray &hash);
signals:
void RequestSibling(NodeDependency dep);
void CachedFrame(RenderTexturePtr texture, const rational& time, const QByteArray& hash);
void FrameSkipped(const rational& time, const QByteArray& hash);
void Downloaded(const QByteArray& hash);
private:
void WakeCaller();
VideoRenderBackend* parent_;
QOpenGLContext* share_ctx_;
RenderInstance render_instance_;
RenderTexturePtr texture_;
GLuint read_buffer_;
QOpenGLFunctions* f;
QOpenGLExtraFunctions* xf;
PixelFormatInfo format_info_;
OIIO::ImageSpec spec_;
QVector<uchar> data_buffer_;
};
using RendererThreadPtr = std::shared_ptr<VideoRendererThreadBase>;
#endif // RENDERTHREAD_H
+3 -3
View File
@@ -84,7 +84,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
ruler_->SetScale(48.0);
// Start background renderers
video_renderer_ = new VideoRenderBackend(this);
video_renderer_ = new OpenGLBackend(gl_widget_->context(), this);
connect(video_renderer_, SIGNAL(CachedFrameReady(const rational&)), this, SLOT(RendererCachedFrame(const rational&)));
}
@@ -168,7 +168,7 @@ void ViewerWidget::DisconnectViewerNode()
ConnectViewerNode(nullptr);
}
void ViewerWidget::SetTexture(RenderTexturePtr tex)
void ViewerWidget::SetTexture(OpenGLTexturePtr tex)
{
if (tex == nullptr) {
gl_widget_->SetTexture(0);
@@ -197,7 +197,7 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time)
if (viewer_node_ == nullptr) {
SetTexture(nullptr);
} else {
SetTexture(video_renderer_->GetCachedFrame(time));
SetTexture(video_renderer_->GetCachedFrameAsTexture(time));
}
}
+4 -3
View File
@@ -30,7 +30,8 @@
#include "common/rational.h"
#include "node/output/viewer/viewer.h"
#include "render/backend/videorenderbackend.h"
#include "render/backend/opengl/openglbackend.h"
#include "render/backend/opengl/opengltexture.h"
#include "viewerglwidget.h"
#include "viewersizer.h"
#include "widget/playbackcontrols/playbackcontrols.h"
@@ -73,7 +74,7 @@ public slots:
*
* @param tex
*/
void SetTexture(RenderTexturePtr tex);
void SetTexture(OpenGLTexturePtr tex);
void SetTimebase(const rational& r);
@@ -110,7 +111,7 @@ private:
void PushScrubbedAudio();
VideoRenderBackend* video_renderer_;
OpenGLBackend* video_renderer_;
ViewerSizer* sizer_;