foundations for background caching
This commit is contained in:
@@ -18,6 +18,8 @@ set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/processor/renderer/renderer.h
|
||||
node/processor/renderer/renderer.cpp
|
||||
node/processor/renderer/rendererprobe.h
|
||||
node/processor/renderer/rendererprobe.cpp
|
||||
node/processor/renderer/rendererthread.h
|
||||
node/processor/renderer/rendererthread.cpp
|
||||
PARENT_SCOPE
|
||||
|
||||
@@ -20,10 +20,19 @@
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QImage>
|
||||
#include <QtMath>
|
||||
|
||||
#include "render/rendertypes.h"
|
||||
#include "rendererprobe.h"
|
||||
|
||||
RendererProcessor::RendererProcessor() :
|
||||
started_(false)
|
||||
started_(false),
|
||||
width_(0),
|
||||
height_(0)
|
||||
{
|
||||
texture_input_ = new NodeInput("tex_in");
|
||||
texture_input_->add_data_input(NodeInput::kTexture);
|
||||
@@ -54,27 +63,73 @@ QString RendererProcessor::id()
|
||||
return "org.olivevideoeditor.Olive.renderervenus";
|
||||
}
|
||||
|
||||
void RendererProcessor::SetCacheName(const QString &s)
|
||||
{
|
||||
cache_name_ = s;
|
||||
cache_time_ = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
GenerateCacheIDInternal();
|
||||
}
|
||||
|
||||
void RendererProcessor::Process(const rational &time)
|
||||
{
|
||||
Q_UNUSED(time)
|
||||
|
||||
texture_output_->set_value(0);
|
||||
|
||||
if (!texture_input_->IsConnected()) {
|
||||
// Nothing is connected - nothing to show or render
|
||||
return;
|
||||
}
|
||||
|
||||
if (cache_id_.isEmpty()) {
|
||||
qWarning() << "RendererProcessor has no cache ID";
|
||||
return;
|
||||
}
|
||||
|
||||
if (timebase_.isNull()) {
|
||||
qWarning() << "RendererProcessor has no timebase";
|
||||
return;
|
||||
}
|
||||
|
||||
// This Renderer node relies on a disk cache so this Process() function should be quite fast. Either it returns the
|
||||
// cached frame or it returns nothing.
|
||||
|
||||
// Perhaps it should lookahead to load textures into VRAM in advance?
|
||||
|
||||
// Should it cache the final result or the result in an 8-bit image or a 16-bit intermediate image?
|
||||
|
||||
/*qDebug() << QString("Requesting %1/%2").arg(QString::number(time.numerator()), QString::number(time.denominator()));
|
||||
|
||||
if (cache_map_.contains(time)) {
|
||||
qDebug() << " We have this frame!";
|
||||
} else {
|
||||
qDebug() << " No frame at this address";
|
||||
cache_map_.insert(time, true);
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// FIXME: Test code only
|
||||
GLuint tex = texture_input_->get_value(time).value<GLuint>();
|
||||
//GLuint tex = texture_input_->get_value(time).value<GLuint>();
|
||||
|
||||
//glReadPixels()
|
||||
|
||||
texture_output_->set_value(tex);
|
||||
//texture_output_->set_value(tex);
|
||||
// End test code
|
||||
|
||||
/*
|
||||
// Ensure we have started
|
||||
Start();
|
||||
|
||||
if (!started_) {
|
||||
qWarning() << tr("An error occurred starting the Renderer node");
|
||||
return;
|
||||
}
|
||||
Start();
|
||||
|
||||
if (!started_) {
|
||||
qWarning() << tr("An error occurred starting the Renderer node");
|
||||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -85,8 +140,45 @@ void RendererProcessor::Release()
|
||||
|
||||
void RendererProcessor::InvalidateCache(const rational &start_range, const rational &end_range)
|
||||
{
|
||||
Q_UNUSED(start_range)
|
||||
Q_UNUSED(end_range)
|
||||
qDebug() << "[RendererProcessor] Cache invalidated between"
|
||||
<< start_range.toDouble()
|
||||
<< "and"
|
||||
<< end_range.toDouble();
|
||||
|
||||
for (rational r=start_range;r<=end_range;r+=timebase_) {
|
||||
if (!cache_queue_.contains(r)) {
|
||||
cache_queue_.append(r);
|
||||
}
|
||||
}
|
||||
|
||||
CacheCallback();
|
||||
|
||||
Node::InvalidateCache(start_range, end_range);
|
||||
}
|
||||
|
||||
void RendererProcessor::SetTimebase(const rational &timebase)
|
||||
{
|
||||
timebase_ = timebase;
|
||||
timebase_dbl_ = timebase_.toDouble();
|
||||
}
|
||||
|
||||
void RendererProcessor::SetParameters(const int &width,
|
||||
const int &height,
|
||||
const olive::PixelFormat &format,
|
||||
const olive::RenderMode &mode)
|
||||
{
|
||||
// Since we're changing parameters, all the existing threads are invalid and must be removed. They will start again
|
||||
// next time this Node has to process anything.
|
||||
Stop();
|
||||
|
||||
// Set new parameters
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
mode_ = mode;
|
||||
|
||||
// Regenerate the cache ID
|
||||
GenerateCacheIDInternal();
|
||||
}
|
||||
|
||||
void RendererProcessor::Start()
|
||||
@@ -98,8 +190,8 @@ void RendererProcessor::Start()
|
||||
threads_.resize(QThread::idealThreadCount());
|
||||
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
threads_[i] = std::make_shared<RendererThread>();
|
||||
threads_[i]->run();
|
||||
threads_[i] = std::make_shared<RendererThread>(width_, height_, format_, mode_);
|
||||
threads_[i]->StartThread(QThread::HighPriority);
|
||||
}
|
||||
|
||||
started_ = true;
|
||||
@@ -120,11 +212,77 @@ void RendererProcessor::Stop()
|
||||
threads_.clear();
|
||||
}
|
||||
|
||||
void RendererProcessor::GenerateCacheIDInternal()
|
||||
{
|
||||
if (cache_name_.isEmpty() || width_ == 0 || height_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate an ID that is more or less guaranteed to be unique to this Sequence
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(cache_name_.toUtf8());
|
||||
hash.addData(QString::number(cache_time_).toUtf8());
|
||||
hash.addData(QString::number(width_).toUtf8());
|
||||
hash.addData(QString::number(height_).toUtf8());
|
||||
hash.addData(QString::number(format_).toUtf8());
|
||||
|
||||
QByteArray bytes = hash.result();
|
||||
cache_id_ = bytes.toHex();
|
||||
}
|
||||
|
||||
void RendererProcessor::CacheCallback()
|
||||
{
|
||||
if (cache_queue_.isEmpty() || !texture_input_->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
rational time_to_cache = cache_queue_.first();
|
||||
|
||||
Node* node_to_cache = texture_input_->edges().first()->output()->parent();
|
||||
|
||||
RendererProbe::ProbeNode(node_to_cache, QThread::idealThreadCount(), time_to_cache);
|
||||
|
||||
/*
|
||||
// Make sure cache has started
|
||||
Start();
|
||||
|
||||
bool caching = false;
|
||||
|
||||
// Look for a thread that's available
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
RendererThreadPtr thread = threads_.at(i);
|
||||
|
||||
if (thread->Queue(node_to_cache, time_to_cache)) {
|
||||
// This thread is free and we've just taken control of it
|
||||
caching = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (caching) {
|
||||
cache_queue_.removeFirst();
|
||||
|
||||
qDebug() << "[RendererProcessor] Ready to cache" << time_to_cache.numerator() << "/" << time_to_cache.denominator();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
RendererThread* RendererProcessor::CurrentThread()
|
||||
{
|
||||
return dynamic_cast<RendererThread*>(QThread::currentThread());
|
||||
}
|
||||
|
||||
RenderInstance *RendererProcessor::CurrentInstance()
|
||||
{
|
||||
RendererThread* thread = CurrentThread();
|
||||
|
||||
if (thread != nullptr) {
|
||||
return thread->render_instance();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeInput *RendererProcessor::texture_input()
|
||||
{
|
||||
return texture_input_;
|
||||
|
||||
@@ -21,7 +21,11 @@
|
||||
#ifndef RENDERER_H
|
||||
#define RENDERER_H
|
||||
|
||||
#include <QOpenGLTexture>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "render/pixelformat.h"
|
||||
#include "render/rendermodes.h"
|
||||
#include "rendererthread.h"
|
||||
|
||||
/**
|
||||
@@ -44,10 +48,14 @@ public:
|
||||
virtual QString Description() override;
|
||||
virtual QString id() override;
|
||||
|
||||
void SetCacheName(const QString& s);
|
||||
|
||||
virtual void Release() override;
|
||||
|
||||
virtual void InvalidateCache(const rational &start_range, const rational &end_range) override;
|
||||
|
||||
void SetTimebase(const rational& timebase);
|
||||
|
||||
/**
|
||||
* @brief Set parameters of the Renderer
|
||||
*
|
||||
@@ -66,7 +74,10 @@ public:
|
||||
*
|
||||
* Buffer pixel format
|
||||
*/
|
||||
void SetParameters(const int& width, const int& height, const olive::PixelFormat& format);
|
||||
void SetParameters(const int& width,
|
||||
const int& height,
|
||||
const olive::PixelFormat& format,
|
||||
const olive::RenderMode& mode);
|
||||
|
||||
/**
|
||||
* @brief Return current instance of a RenderThread (or nullptr if there is none)
|
||||
@@ -74,13 +85,15 @@ public:
|
||||
* This function attempts a dynamic_cast on QThread::currentThread() to RendererThread, which will return nullptr if
|
||||
* the cast fails (e.g. if this function is called from the main thread rather than a RendererThread).
|
||||
*/
|
||||
static RendererThread *CurrentThread();
|
||||
static RendererThread* CurrentThread();
|
||||
|
||||
static RenderInstance* CurrentInstance();
|
||||
|
||||
NodeInput* texture_input();
|
||||
|
||||
NodeOutput* texture_output();
|
||||
|
||||
public slots:
|
||||
protected:
|
||||
virtual void Process(const rational &time) override;
|
||||
|
||||
private:
|
||||
@@ -94,13 +107,48 @@ private:
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* @brief Internal function for generating the cache ID
|
||||
*/
|
||||
void GenerateCacheIDInternal();
|
||||
|
||||
/**
|
||||
* @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 CacheCallback();
|
||||
|
||||
/**
|
||||
* @brief Internal list of RenderThreads
|
||||
*/
|
||||
QVector<RendererThreadPtr> threads_;
|
||||
|
||||
/**
|
||||
* @brief Internal variable that contains whether the Renderer has started or not
|
||||
*/
|
||||
bool started_;
|
||||
|
||||
NodeInput* texture_input_;
|
||||
|
||||
NodeOutput* texture_output_;
|
||||
|
||||
int width_;
|
||||
|
||||
int height_;
|
||||
|
||||
olive::PixelFormat format_;
|
||||
|
||||
olive::RenderMode mode_;
|
||||
|
||||
rational timebase_;
|
||||
double timebase_dbl_;
|
||||
|
||||
QVector<rational> cache_queue_;
|
||||
QString cache_name_;
|
||||
qint64 cache_time_;
|
||||
QString cache_id_;
|
||||
|
||||
};
|
||||
|
||||
#endif // RENDERER_H
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "rendererprobe.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
RendererProbe::RendererProbe()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RendererProbe::ProbeNode(Node *node, int thread_count, const rational& time)
|
||||
{
|
||||
Q_ASSERT(thread_count > 0);
|
||||
|
||||
//QVector< QVector<Node*> > dependency_graph;
|
||||
|
||||
//dependency_graph.resize(thread_count);
|
||||
|
||||
TraverseNode(node, 0, time);
|
||||
}
|
||||
|
||||
void RendererProbe::TraverseNode(Node *node, int thread, const rational& time)
|
||||
{
|
||||
qDebug() << node << "will run on thread" << thread;
|
||||
|
||||
QList<Node*> deps = node->GetImmediateDependenciesAt(time);
|
||||
|
||||
foreach (Node* dep, deps) {
|
||||
qDebug() << " Dependency found:" << dep;
|
||||
}
|
||||
|
||||
foreach (Node* dep, deps) {
|
||||
TraverseNode(dep, thread, time);
|
||||
thread++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef RENDERERPROBE_H
|
||||
#define RENDERERPROBE_H
|
||||
|
||||
#include "node/node.h"
|
||||
|
||||
class RendererProbe
|
||||
{
|
||||
public:
|
||||
RendererProbe();
|
||||
|
||||
static void ProbeNode(Node* node, int thread_count, const rational &time);
|
||||
|
||||
private:
|
||||
static void TraverseNode(Node* node, int thread, const rational& time);
|
||||
};
|
||||
|
||||
#endif // RENDERERPROBE_H
|
||||
@@ -22,8 +22,13 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
RendererThread::RendererThread() :
|
||||
cancelled_(false)
|
||||
RendererThread::RendererThread(const int &width, const int &height, const olive::PixelFormat &format, const olive::RenderMode &mode) :
|
||||
cancelled_(false),
|
||||
width_(width),
|
||||
height_(height),
|
||||
format_(format),
|
||||
mode_(mode),
|
||||
render_instance_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -32,16 +37,22 @@ bool RendererThread::Queue(Node *n, const rational& time)
|
||||
// If the thread is inactive, tryLock() will succeed
|
||||
if (mutex_.tryLock()) {
|
||||
|
||||
qDebug() << "[RendererThread Main] tryLock succeeded";
|
||||
|
||||
// The mutex is locked in the calling thread now, so we can change the active Node
|
||||
node_ = n;
|
||||
time_ = time;
|
||||
|
||||
// We can now wake up our main thread
|
||||
qDebug() << "[RendererThread Main] Waking thread";
|
||||
wait_cond_.wakeAll();
|
||||
qDebug() << "[RendererThread Main] Unlocking mutex";
|
||||
mutex_.unlock();
|
||||
|
||||
// Wait for thread to start before returning
|
||||
qDebug() << "[RendererThread Main] Waiting for caller mutex";
|
||||
caller_mutex_.lock();
|
||||
qDebug() << "[RendererThread Main] Caller mutex arrived";
|
||||
caller_mutex_.unlock();
|
||||
|
||||
return true;
|
||||
@@ -59,48 +70,50 @@ void RendererThread::Cancel()
|
||||
wait();
|
||||
}
|
||||
|
||||
RenderInstance *RendererThread::render_instance()
|
||||
{
|
||||
return render_instance_;
|
||||
}
|
||||
|
||||
void RendererThread::run()
|
||||
{
|
||||
// Create OpenGL context (automatically destroys any existing if there is one)
|
||||
if (!ctx_.create()) {
|
||||
qWarning() << tr("Failed to create OpenGL context in thread %1").arg(reinterpret_cast<quintptr>(this));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create offscreen surface
|
||||
surface_.create();
|
||||
|
||||
// Make context current on that surface
|
||||
if (!ctx_.makeCurrent(&surface_)) {
|
||||
qWarning() << tr("Failed to makeCurrent() on offscreen surface in thread %1").arg(reinterpret_cast<quintptr>(this));
|
||||
surface_.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Lock mutex for main loop
|
||||
mutex_.lock();
|
||||
|
||||
// Main loop (use Cancel() to exit it)
|
||||
while (!cancelled_) {
|
||||
// Lock the caller mutex (used in Queue() for thread synchronization)
|
||||
caller_mutex_.lock();
|
||||
RenderInstance instance(width_, height_, format_, mode_);
|
||||
render_instance_ = &instance;
|
||||
|
||||
// Main waiting condition
|
||||
wait_cond_.wait(&mutex_);
|
||||
// Allocate and create resources
|
||||
if (instance.Start()) {
|
||||
|
||||
// Unlock the caller mutex
|
||||
caller_mutex_.unlock();
|
||||
// Main loop (use Cancel() to exit it)
|
||||
while (!cancelled_) {
|
||||
// Lock the caller mutex (used in Queue() for thread synchronization)
|
||||
caller_mutex_.lock();
|
||||
|
||||
// Process the Node
|
||||
node_->Process(time_);
|
||||
// Main waiting condition
|
||||
wait_cond_.wait(&mutex_);
|
||||
|
||||
// Unlock the caller mutex
|
||||
caller_mutex_.unlock();
|
||||
|
||||
// Process the Node
|
||||
node_->Run(time_);
|
||||
}
|
||||
}
|
||||
|
||||
// Release OpenGL context
|
||||
ctx_.doneCurrent();
|
||||
|
||||
// Destroy offscreen surface
|
||||
surface_.destroy();
|
||||
// Free all resources
|
||||
render_instance_ = nullptr;
|
||||
instance.Stop();
|
||||
|
||||
// Unlock mutex before exiting
|
||||
mutex_.unlock();
|
||||
}
|
||||
|
||||
void RendererThread::StartThread(QThread::Priority priority)
|
||||
{
|
||||
queue_.clear();
|
||||
|
||||
// Start the thread (the thread will unlock caller_mutex_)
|
||||
QThread::start(priority);
|
||||
}
|
||||
|
||||
@@ -23,36 +23,36 @@
|
||||
|
||||
#include <memory>
|
||||
#include <QMutex>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#include <QThread>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "render/texturebuffer.h"
|
||||
#include "render/renderinstance.h"
|
||||
|
||||
struct RenderQueueEntry {
|
||||
Node* node;
|
||||
rational time;
|
||||
};
|
||||
|
||||
class RendererThread : public QThread
|
||||
{
|
||||
public:
|
||||
RendererThread();
|
||||
|
||||
QOpenGLContext* context();
|
||||
|
||||
TextureBuffer* buffer();
|
||||
RendererThread(const int& width,
|
||||
const int& height,
|
||||
const olive::PixelFormat& format,
|
||||
const olive::RenderMode& mode);
|
||||
|
||||
bool Queue(Node* n, const rational &time);
|
||||
|
||||
void Cancel();
|
||||
|
||||
RenderInstance* render_instance();
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
void StartThread(Priority priority = InheritPriority);
|
||||
|
||||
private:
|
||||
QOpenGLContext ctx_;
|
||||
|
||||
QOffscreenSurface surface_;
|
||||
|
||||
TextureBuffer buffer_;
|
||||
|
||||
QWaitCondition wait_cond_;
|
||||
|
||||
QMutex mutex_;
|
||||
@@ -64,6 +64,19 @@ private:
|
||||
rational time_;
|
||||
|
||||
bool cancelled_;
|
||||
|
||||
const int& width_;
|
||||
|
||||
const int& height_;
|
||||
|
||||
const olive::PixelFormat& format_;
|
||||
|
||||
const olive::RenderMode& mode_;
|
||||
|
||||
RenderInstance* render_instance_;
|
||||
|
||||
QVector<RenderQueueEntry> queue_;
|
||||
|
||||
};
|
||||
|
||||
using RendererThreadPtr = std::shared_ptr<RendererThread>;
|
||||
|
||||
Reference in New Issue
Block a user