foundations for background caching

This commit is contained in:
itsmattkc
2019-08-24 22:16:01 +10:00
parent 9439494c10
commit 438e7081f9
39 changed files with 919 additions and 213 deletions
@@ -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
+170 -12
View File
@@ -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_;
+51 -3
View File
@@ -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
+46 -33
View File
@@ -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);
}
+27 -14
View File
@@ -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>;