overhauled render classes to support video AND audio
Major refactoring work to try sharing as much code as possible between the video renderers and audio renderers, as well as make them as platform-independent as possible.
This commit is contained in:
+30
-17
@@ -27,23 +27,23 @@ const uint64_t &AudioParams::channel_layout() const
|
||||
}
|
||||
|
||||
AudioRenderingParams::AudioRenderingParams() :
|
||||
format_(olive::SAMPLE_FMT_INVALID)
|
||||
format_(SAMPLE_FMT_INVALID)
|
||||
{
|
||||
}
|
||||
|
||||
AudioRenderingParams::AudioRenderingParams(const int &sample_rate, const uint64_t &channel_layout, const olive::SampleFormat &format) :
|
||||
AudioRenderingParams::AudioRenderingParams(const int &sample_rate, const uint64_t &channel_layout, const SampleFormat &format) :
|
||||
AudioParams(sample_rate, channel_layout),
|
||||
format_(format)
|
||||
{
|
||||
}
|
||||
|
||||
AudioRenderingParams::AudioRenderingParams(const AudioParams ¶ms, const olive::SampleFormat &format) :
|
||||
AudioRenderingParams::AudioRenderingParams(const AudioParams ¶ms, const SampleFormat &format) :
|
||||
AudioParams(params),
|
||||
format_(format)
|
||||
{
|
||||
}
|
||||
|
||||
const olive::SampleFormat &AudioRenderingParams::format() const
|
||||
const SampleFormat &AudioRenderingParams::format() const
|
||||
{
|
||||
return format_;
|
||||
}
|
||||
@@ -52,7 +52,14 @@ int AudioRenderingParams::time_to_bytes(const rational &time) const
|
||||
{
|
||||
Q_ASSERT(is_valid());
|
||||
|
||||
return qFloor(time.toDouble() * sample_rate()) * channel_count() * bytes_per_sample_per_channel();
|
||||
return time_to_samples(time) * channel_count() * bytes_per_sample_per_channel();
|
||||
}
|
||||
|
||||
int AudioRenderingParams::time_to_samples(const rational &time) const
|
||||
{
|
||||
Q_ASSERT(is_valid());
|
||||
|
||||
return qFloor(time.toDouble() * sample_rate());
|
||||
}
|
||||
|
||||
int AudioRenderingParams::samples_to_bytes(const int &samples) const
|
||||
@@ -70,18 +77,18 @@ int AudioRenderingParams::channel_count() const
|
||||
int AudioRenderingParams::bytes_per_sample_per_channel() const
|
||||
{
|
||||
switch (format_) {
|
||||
case olive::SAMPLE_FMT_U8:
|
||||
case SAMPLE_FMT_U8:
|
||||
return 1;
|
||||
case olive::SAMPLE_FMT_S16:
|
||||
case SAMPLE_FMT_S16:
|
||||
return 2;
|
||||
case olive::SAMPLE_FMT_S32:
|
||||
case olive::SAMPLE_FMT_FLT:
|
||||
case SAMPLE_FMT_S32:
|
||||
case SAMPLE_FMT_FLT:
|
||||
return 4;
|
||||
case olive::SAMPLE_FMT_DBL:
|
||||
case olive::SAMPLE_FMT_S64:
|
||||
case SAMPLE_FMT_DBL:
|
||||
case SAMPLE_FMT_S64:
|
||||
return 8;
|
||||
case olive::SAMPLE_FMT_INVALID:
|
||||
case olive::SAMPLE_FMT_COUNT:
|
||||
case SAMPLE_FMT_INVALID:
|
||||
case SAMPLE_FMT_COUNT:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -95,8 +102,14 @@ int AudioRenderingParams::bits_per_sample() const
|
||||
|
||||
bool AudioRenderingParams::is_valid() const
|
||||
{
|
||||
return (sample_rate() > 0
|
||||
&& channel_layout() > 0
|
||||
&& format_ != olive::SAMPLE_FMT_INVALID
|
||||
&& format_ != olive::SAMPLE_FMT_COUNT);
|
||||
bool valid = (sample_rate() > 0
|
||||
&& channel_layout() > 0
|
||||
&& format_ != SAMPLE_FMT_INVALID
|
||||
&& format_ != SAMPLE_FMT_COUNT);
|
||||
|
||||
if (!valid) {
|
||||
qWarning() << "Invalid params found:" << sample_rate() << channel_layout() << format();
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@@ -25,20 +25,21 @@ private:
|
||||
class AudioRenderingParams : public AudioParams {
|
||||
public:
|
||||
AudioRenderingParams();
|
||||
AudioRenderingParams(const int& sample_rate, const uint64_t& channel_layout, const olive::SampleFormat& format);
|
||||
AudioRenderingParams(const AudioParams& params, const olive::SampleFormat& format);
|
||||
AudioRenderingParams(const int& sample_rate, const uint64_t& channel_layout, const SampleFormat& format);
|
||||
AudioRenderingParams(const AudioParams& params, const SampleFormat& format);
|
||||
|
||||
int time_to_bytes(const rational& time) const;
|
||||
int time_to_samples(const rational& time) const;
|
||||
int samples_to_bytes(const int& samples) const;
|
||||
int channel_count() const;
|
||||
int bytes_per_sample_per_channel() const;
|
||||
int bits_per_sample() const;
|
||||
bool is_valid() const;
|
||||
|
||||
const olive::SampleFormat& format() const;
|
||||
const SampleFormat& format() const;
|
||||
|
||||
private:
|
||||
olive::SampleFormat format_;
|
||||
SampleFormat format_;
|
||||
};
|
||||
|
||||
#endif // AUDIOPARAMS_H
|
||||
|
||||
@@ -18,5 +18,7 @@ set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
render/backend/audio/audiobackend.h
|
||||
render/backend/audio/audiobackend.cpp
|
||||
render/backend/audio/audioworker.h
|
||||
render/backend/audio/audioworker.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
#include "audiobackend.h"
|
||||
|
||||
#include "audioworker.h"
|
||||
|
||||
AudioBackend::AudioBackend(QObject *parent) :
|
||||
AudioRenderBackend(parent)
|
||||
{
|
||||
}
|
||||
|
||||
AudioBackend::~AudioBackend()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
QIODevice *AudioBackend::GetAudioPullDevice()
|
||||
{
|
||||
pull_device_.setFileName(CachePathName());
|
||||
|
||||
return &pull_device_;
|
||||
}
|
||||
|
||||
bool AudioBackend::InitInternal()
|
||||
{
|
||||
// This backend doesn't init anything yet
|
||||
// Initiate one thread per CPU core
|
||||
for (int i=0;i<threads().size();i++) {
|
||||
// Create one processor object for each thread
|
||||
AudioWorker* processor = new AudioWorker(decoder_cache());
|
||||
processor->SetParameters(params());
|
||||
processors_.append(processor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -26,3 +47,34 @@ void AudioBackend::DecompileInternal()
|
||||
{
|
||||
// This backend doesn't compile anything yet
|
||||
}
|
||||
|
||||
void AudioBackend::ConnectWorkerToThis(RenderWorker *worker)
|
||||
{
|
||||
connect(worker, SIGNAL(CompletedCache(NodeDependency)), this, SLOT(ThreadCompletedCache(NodeDependency)));
|
||||
}
|
||||
|
||||
void AudioBackend::ThreadCompletedCache(NodeDependency dep)
|
||||
{
|
||||
caching_ = false;
|
||||
|
||||
QByteArray cached_samples = dep.node()->get_cached_value(dep.range()).toByteArray();
|
||||
|
||||
int offset = params().time_to_bytes(dep.in());
|
||||
int length = params().time_to_bytes(dep.range().length());
|
||||
int out_point = offset + length;
|
||||
|
||||
if (pcm_data_.size() < out_point) {
|
||||
pcm_data_.resize(out_point);
|
||||
}
|
||||
|
||||
// Replace data with this data
|
||||
memcpy(pcm_data_.data() + offset, cached_samples.data(), static_cast<size_t>(length));
|
||||
|
||||
QFile f(CachePathName());
|
||||
if (f.open(QFile::WriteOnly)) {
|
||||
f.write(pcm_data_);
|
||||
f.close();
|
||||
}
|
||||
|
||||
CacheNext();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef AUDIOBACKEND_H
|
||||
#define AUDIOBACKEND_H
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "../audiorenderbackend.h"
|
||||
|
||||
class AudioBackend : public AudioRenderBackend
|
||||
@@ -9,6 +11,10 @@ class AudioBackend : public AudioRenderBackend
|
||||
public:
|
||||
AudioBackend(QObject* parent = nullptr);
|
||||
|
||||
virtual ~AudioBackend() override;
|
||||
|
||||
virtual QIODevice* GetAudioPullDevice() override;
|
||||
|
||||
protected:
|
||||
virtual bool InitInternal() override;
|
||||
|
||||
@@ -18,7 +24,13 @@ protected:
|
||||
|
||||
virtual void DecompileInternal() override;
|
||||
|
||||
virtual void ConnectWorkerToThis(RenderWorker* worker) override;
|
||||
|
||||
private slots:
|
||||
void ThreadCompletedCache(NodeDependency dep);
|
||||
|
||||
private:
|
||||
QFile pull_device_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
#include "audioworker.h"
|
||||
|
||||
AudioWorker::AudioWorker()
|
||||
AudioWorker::AudioWorker(DecoderCache *decoder_cache, QObject *parent) :
|
||||
AudioRenderWorker(decoder_cache, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant AudioWorker::FrameToValue(FramePtr frame)
|
||||
{
|
||||
return frame->ToByteArray();
|
||||
}
|
||||
|
||||
bool AudioWorker::OutputIsAccelerated(NodeOutput *output)
|
||||
{
|
||||
Q_UNUSED(output)
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant AudioWorker::RunNodeAccelerated(NodeOutput *output)
|
||||
{
|
||||
Q_UNUSED(output)
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
#ifndef AUDIOWORKER_H
|
||||
#define AUDIOWORKER_H
|
||||
|
||||
#include "../audiorenderworker.h"
|
||||
|
||||
class AudioWorker
|
||||
class AudioWorker : public AudioRenderWorker
|
||||
{
|
||||
public:
|
||||
AudioWorker();
|
||||
AudioWorker(DecoderCache* decoder_cache, QObject* parent = nullptr);
|
||||
|
||||
protected:
|
||||
virtual QVariant FrameToValue(FramePtr frame) override;
|
||||
|
||||
virtual bool OutputIsAccelerated(NodeOutput *output) override;
|
||||
|
||||
virtual QVariant RunNodeAccelerated(NodeOutput *output) override;
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
#endif // AUDIOWORKER_H
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "audiorenderbackend.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QtMath>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
|
||||
AudioRenderBackend::AudioRenderBackend(QObject *parent) :
|
||||
RenderBackend(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms)
|
||||
@@ -23,8 +25,17 @@ void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms)
|
||||
|
||||
void AudioRenderBackend::InvalidateCache(const rational &start_range, const rational &end_range)
|
||||
{
|
||||
rational start_range_adj = qMax(rational(0), start_range);
|
||||
rational end_range_adj = qMin(viewer_node()->Length(), end_range);
|
||||
|
||||
// Truncate to length if necessary
|
||||
int max_length_in_bytes = params().time_to_bytes(viewer_node()->Length());
|
||||
if (pcm_data_.size() > max_length_in_bytes) {
|
||||
pcm_data_.resize(max_length_in_bytes);
|
||||
}
|
||||
|
||||
// Add the range to the list
|
||||
cache_queue_.append(TimeRange(start_range, end_range));
|
||||
cache_queue_.append(TimeRange(start_range_adj, end_range_adj));
|
||||
|
||||
// Remove any overlaps so we don't render the same thing twice
|
||||
ValidateRanges();
|
||||
@@ -37,7 +48,7 @@ void AudioRenderBackend::ViewerNodeChangedEvent(ViewerOutput *node)
|
||||
{
|
||||
if (node != nullptr) {
|
||||
// FIXME: Hardcoded format
|
||||
SetParameters(AudioRenderingParams(node->audio_params(), olive::SAMPLE_FMT_FLT));
|
||||
SetParameters(AudioRenderingParams(node->audio_params(), SAMPLE_FMT_FLT));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +66,16 @@ bool AudioRenderBackend::GenerateCacheIDInternal(QCryptographicHash &hash)
|
||||
return true;
|
||||
}
|
||||
|
||||
const AudioRenderingParams &AudioRenderBackend::params()
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
NodeInput *AudioRenderBackend::GetDependentInput()
|
||||
{
|
||||
return viewer_node()->samples_input();
|
||||
}
|
||||
|
||||
void AudioRenderBackend::ValidateRanges()
|
||||
{
|
||||
for (int i=0;i<cache_queue_.size();i++) {
|
||||
@@ -75,6 +96,14 @@ void AudioRenderBackend::ValidateRanges()
|
||||
}
|
||||
}
|
||||
|
||||
QString AudioRenderBackend::CachePathName()
|
||||
{
|
||||
QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id());
|
||||
this_cache_dir.mkpath(".");
|
||||
|
||||
return this_cache_dir.filePath(QStringLiteral("pcm"));
|
||||
}
|
||||
|
||||
TimeRange AudioRenderBackend::CombineRange(const TimeRange &a, const TimeRange &b)
|
||||
{
|
||||
return TimeRange(qMin(a.in(), b.in()),
|
||||
@@ -83,5 +112,5 @@ TimeRange AudioRenderBackend::CombineRange(const TimeRange &a, const TimeRange &
|
||||
|
||||
bool AudioRenderBackend::RangesOverlap(const TimeRange &a, const TimeRange &b)
|
||||
{
|
||||
return (a.out() < b.in() && a.in() > b.out());
|
||||
return !(a.out() < b.in() || a.in() > b.out());
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ public:
|
||||
*/
|
||||
void SetParameters(const AudioRenderingParams ¶ms);
|
||||
|
||||
virtual QIODevice* GetAudioPullDevice() = 0;
|
||||
|
||||
const AudioRenderingParams& params();
|
||||
|
||||
public slots:
|
||||
virtual void InvalidateCache(const rational &start_range, const rational &end_range) override;
|
||||
|
||||
@@ -29,7 +33,11 @@ protected:
|
||||
*/
|
||||
virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) override;
|
||||
|
||||
//virtual void CacheIDChangedEvent(const QString& id) override;
|
||||
virtual NodeInput* GetDependentInput() override;
|
||||
|
||||
QString CachePathName();
|
||||
|
||||
QByteArray pcm_data_;
|
||||
|
||||
private:
|
||||
void ValidateRanges();
|
||||
@@ -40,8 +48,6 @@ private:
|
||||
|
||||
AudioRenderingParams params_;
|
||||
|
||||
QByteArray pcm_data_;
|
||||
|
||||
};
|
||||
|
||||
#endif // AUDIORENDERBACKEND_H
|
||||
|
||||
@@ -1,13 +1,54 @@
|
||||
#include "audiorenderworker.h"
|
||||
|
||||
#include "audio/audiomanager.h"
|
||||
|
||||
AudioRenderWorker::AudioRenderWorker(DecoderCache *decoder_cache, QObject *parent) :
|
||||
RenderWorker(decoder_cache, parent)
|
||||
{
|
||||
}
|
||||
|
||||
void AudioRenderWorker::SetParameters(const AudioRenderingParams &audio_params)
|
||||
{
|
||||
audio_params_ = audio_params;
|
||||
}
|
||||
|
||||
void AudioRenderWorker::RenderAsSibling(NodeDependency dep)
|
||||
{
|
||||
NodeOutput* output = dep.node();
|
||||
Node* node = output->parent();
|
||||
QList<NodeInput*> connected_inputs;
|
||||
QVariant value;
|
||||
|
||||
// Set working state
|
||||
working_++;
|
||||
|
||||
bool locked = false;
|
||||
|
||||
// Firstly we check if this node is a "Block", if it is that means it's part of a linked list of mutually exclusive
|
||||
// nodes based on time and we might need to locate which Block to attach to
|
||||
if (node->IsBlock()
|
||||
&& (dep.range().in() < static_cast<Block*>(node)->in()
|
||||
|| dep.range().out() > static_cast<Block*>(node)->out())) {
|
||||
// If the range is not wholly contained in this Block, we'll need to do some extra processing
|
||||
value = RenderBlock(output, dep.range());
|
||||
} else {
|
||||
node->LockProcessing();
|
||||
value = ProcessNodeNormally(NodeDependency(output, dep.range()));
|
||||
locked = true;
|
||||
}
|
||||
|
||||
if (!locked) {
|
||||
node->LockProcessing();
|
||||
}
|
||||
|
||||
// Place the value into the output
|
||||
output->cache_value(dep.range(), value);
|
||||
|
||||
// We're done!
|
||||
node->UnlockProcessing();
|
||||
|
||||
// End this working state
|
||||
working_--;
|
||||
}
|
||||
|
||||
bool AudioRenderWorker::InitInternal()
|
||||
@@ -20,3 +61,59 @@ void AudioRenderWorker::CloseInternal()
|
||||
{
|
||||
// Nothing to init yet
|
||||
}
|
||||
|
||||
QVariant AudioRenderWorker::RenderBlock(NodeOutput* output, const TimeRange &range)
|
||||
{
|
||||
QList<Block*> active_blocks = ValidateBlockRange(static_cast<Block*>(output->parent()), range);
|
||||
|
||||
// All these blocks will need to output to a buffer so we create one here
|
||||
QByteArray block_range_buffer(audio_params_.time_to_bytes(range.length()), 0);
|
||||
|
||||
// Loop through active blocks retrieving their audio
|
||||
while (!active_blocks.isEmpty()) {
|
||||
int block_for_this_thread = -1;
|
||||
|
||||
for (int i=0;i<active_blocks.size();i++) {
|
||||
Block* b = active_blocks.at(i);
|
||||
NodeOutput* connected_output = static_cast<NodeOutput*>(b->GetParameterWithID(output->id()));
|
||||
|
||||
TimeRange range_for_block(qMax(b->in(), range.in()),
|
||||
qMin(b->out(), range.out()));
|
||||
|
||||
// If the block is locked, we assume another thread has it. Otherwise, we'll work with it
|
||||
if (connected_output->has_cached_value(range_for_block)) {
|
||||
// This output already has this value, no need to process it again
|
||||
QByteArray samples_from_this_block = connected_output->get_cached_value(range_for_block).toByteArray();
|
||||
|
||||
int destination_offset = audio_params_.time_to_bytes(range_for_block.in() - range.in());
|
||||
|
||||
memcpy(block_range_buffer.data()+destination_offset,
|
||||
samples_from_this_block.data(),
|
||||
static_cast<size_t>(samples_from_this_block.size()));
|
||||
|
||||
active_blocks.removeAt(i);
|
||||
i--;
|
||||
} else if (!b->IsProcessingLocked()) {
|
||||
if (block_for_this_thread == -1) {
|
||||
block_for_this_thread = i;
|
||||
} else {
|
||||
emit RequestSibling(NodeDependency(connected_output,
|
||||
range_for_block));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (block_for_this_thread > -1) {
|
||||
Block* b = active_blocks.at(block_for_this_thread);
|
||||
TimeRange range_for_block(qMax(b->in(), range.in()),
|
||||
qMin(b->out(), range.out()));
|
||||
|
||||
RenderAsSibling(NodeDependency(static_cast<NodeOutput*>(b->GetParameterWithID(output->id())),
|
||||
range_for_block));
|
||||
} else {
|
||||
QThread::msleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
return block_range_buffer;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ class AudioRenderWorker : public RenderWorker
|
||||
public:
|
||||
AudioRenderWorker(DecoderCache* decoder_cache, QObject* parent = nullptr);
|
||||
|
||||
void SetParameters(const AudioRenderingParams& audio_params);
|
||||
|
||||
public slots:
|
||||
virtual void RenderAsSibling(NodeDependency dep) override;
|
||||
|
||||
@@ -17,7 +19,10 @@ protected:
|
||||
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
QVariant RenderBlock(NodeOutput *output, const TimeRange& range);
|
||||
|
||||
private:
|
||||
AudioRenderingParams audio_params_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ OpenGLBackend::OpenGLBackend(QObject *parent) :
|
||||
|
||||
OpenGLBackend::~OpenGLBackend()
|
||||
{
|
||||
CloseInternal();
|
||||
Close();
|
||||
}
|
||||
|
||||
bool OpenGLBackend::InitInternal()
|
||||
@@ -32,27 +32,10 @@ bool OpenGLBackend::InitInternal()
|
||||
|
||||
// Initiate one thread per CPU core
|
||||
for (int i=0;i<threads().size();i++) {
|
||||
QThread* thread = threads().at(i);
|
||||
|
||||
// Create one processor object for each thread
|
||||
OpenGLWorker* processor = new OpenGLWorker(share_ctx, &shader_cache_, decoder_cache(), frame_cache());
|
||||
processor->SetParameters(params());
|
||||
|
||||
// Connect to it
|
||||
connect(processor, SIGNAL(RequestSibling(NodeDependency)), this, SLOT(ThreadRequestedSibling(NodeDependency)));
|
||||
connect(processor, SIGNAL(CompletedFrame(NodeDependency, QByteArray)), this, SLOT(ThreadCompletedFrame(NodeDependency, QByteArray)));
|
||||
connect(processor, SIGNAL(HashAlreadyBeingCached()), this, SLOT(ThreadSkippedFrame()));
|
||||
connect(processor, SIGNAL(CompletedDownload(NodeDependency, QByteArray)), this, SLOT(ThreadCompletedDownload(NodeDependency, QByteArray)));
|
||||
connect(processor, SIGNAL(HashAlreadyExists(NodeDependency, QByteArray)), this, SLOT(ThreadHashAlreadyExists(NodeDependency, QByteArray)));
|
||||
|
||||
// Finally, we can move it to its own thread
|
||||
processor->moveToThread(thread);
|
||||
|
||||
// Add processor to list
|
||||
processors_.append(processor);
|
||||
|
||||
// This function blocks the main thread intentionally. See the documentation for this function to see why.
|
||||
processor->Init();
|
||||
}
|
||||
|
||||
// Create master texture (the one sent to the viewer)
|
||||
@@ -77,8 +60,6 @@ void OpenGLBackend::CloseInternal()
|
||||
master_texture_ = nullptr;
|
||||
push_texture_ = nullptr;
|
||||
//copy_pipeline_ = nullptr;
|
||||
|
||||
VideoRenderBackend::Close();
|
||||
}
|
||||
|
||||
OpenGLTexturePtr OpenGLBackend::GetCachedFrameAsTexture(const rational &time)
|
||||
@@ -114,7 +95,7 @@ bool OpenGLBackend::CompileInternal()
|
||||
bool ret = TraverseCompiling(viewer_node());
|
||||
|
||||
if (ret) {
|
||||
qDebug() << "Compiled successfully!";
|
||||
//qDebug() << "Compiled successfully!";
|
||||
compiled_ = true;
|
||||
} else {
|
||||
qDebug() << "Compile failed:" << GetError();
|
||||
@@ -173,7 +154,7 @@ bool OpenGLBackend::TraverseCompiling(Node *n)
|
||||
|
||||
shader_cache_.AddShader(connected_output, program);
|
||||
|
||||
qDebug() << "Compiled" << connected_output->parent()->id() << "->" << connected_output->id();
|
||||
//qDebug() << "Compiled" << connected_output->parent()->id() << "->" << connected_output->id();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,20 +252,6 @@ void OpenGLBackend::ThreadCompletedFrame(NodeDependency path, QByteArray hash)
|
||||
CacheNext();
|
||||
}*/
|
||||
|
||||
void OpenGLBackend::ThreadRequestedSibling(NodeDependency dep)
|
||||
{
|
||||
// Try to queue another thread to run this dep in advance
|
||||
foreach (RenderWorker* worker, processors_) {
|
||||
if (worker->IsAvailable()) {
|
||||
QMetaObject::invokeMethod(worker,
|
||||
"RenderAsSibling",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(NodeDependency, dep));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLBackend::ThreadCompletedDownload(NodeDependency dep, QByteArray hash)
|
||||
{
|
||||
frame_cache()->SetHash(dep.in(), hash);
|
||||
|
||||
@@ -41,7 +41,6 @@ private:
|
||||
|
||||
private slots:
|
||||
void ThreadCompletedFrame(NodeDependency path, QByteArray hash);
|
||||
void ThreadRequestedSibling(NodeDependency dep);
|
||||
void ThreadCompletedDownload(NodeDependency dep, QByteArray hash);
|
||||
void ThreadSkippedFrame();
|
||||
void ThreadHashAlreadyExists(NodeDependency dep, QByteArray hash);
|
||||
|
||||
@@ -47,7 +47,7 @@ bool OpenGLWorker::InitInternal()
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant OpenGLWorker::FrameToTexture(FramePtr frame)
|
||||
QVariant OpenGLWorker::FrameToValue(FramePtr frame)
|
||||
{
|
||||
OpenGLTexturePtr footage_tex = std::make_shared<OpenGLTexture>();
|
||||
footage_tex->Create(ctx_, frame);
|
||||
@@ -63,7 +63,7 @@ QVariant OpenGLWorker::FrameToTexture(FramePtr frame)
|
||||
return QVariant::fromValue(footage_tex);
|
||||
}
|
||||
|
||||
bool OpenGLWorker::OutputIsShader(NodeOutput* output)
|
||||
bool OpenGLWorker::OutputIsAccelerated(NodeOutput* output)
|
||||
{
|
||||
return shader_cache_->HasShader(output);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ void OpenGLWorker::ParametersChangedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
QVariant OpenGLWorker::RunNodeAsShader(NodeOutput *out)
|
||||
QVariant OpenGLWorker::RunNodeAccelerated(NodeOutput *out)
|
||||
{
|
||||
OpenGLShaderPtr shader = shader_cache_->GetShader(out);
|
||||
Node* node = out->parent();
|
||||
@@ -126,6 +126,7 @@ QVariant OpenGLWorker::RunNodeAsShader(NodeOutput *out)
|
||||
shader->setUniformValue(variable_location, input->value().value<QVector4D>());
|
||||
break;
|
||||
case NodeInput::kMatrix:
|
||||
qDebug() << "Setting uniform value to matrix" << input->value().value<QMatrix4x4>();
|
||||
shader->setUniformValue(variable_location, input->value().value<QMatrix4x4>());
|
||||
break;
|
||||
case NodeInput::kColor:
|
||||
@@ -138,14 +139,14 @@ QVariant OpenGLWorker::RunNodeAsShader(NodeOutput *out)
|
||||
case NodeInput::kFootage:
|
||||
{
|
||||
OpenGLTexturePtr texture = input->value().value<OpenGLTexturePtr>();
|
||||
qDebug() << " Binding" << texture->texture() << "from" << input << "to GL_TEXTURE" << input_texture_count;
|
||||
//qDebug() << " Binding" << texture->texture() << "from" << input << "to GL_TEXTURE" << input_texture_count;
|
||||
|
||||
functions_->glActiveTexture(GL_TEXTURE0 + input_texture_count);
|
||||
functions_->glBindTexture(GL_TEXTURE_2D, texture->texture());
|
||||
|
||||
// Set value to bound texture
|
||||
shader->setUniformValue(variable_location, input_texture_count);
|
||||
qDebug() << " Setting" << input->id() << "to" << input_texture_count;
|
||||
//qDebug() << " Setting" << input->id() << "to" << input_texture_count;
|
||||
|
||||
input_texture_count++;
|
||||
break;
|
||||
@@ -165,7 +166,7 @@ QVariant OpenGLWorker::RunNodeAsShader(NodeOutput *out)
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << " Blitting with shader!";
|
||||
//qDebug() << " Blitting with shader!";
|
||||
olive::gl::Blit(shader);
|
||||
|
||||
// Release any textures we bound before
|
||||
|
||||
@@ -45,11 +45,11 @@ protected:
|
||||
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
virtual QVariant FrameToTexture(FramePtr frame) override;
|
||||
virtual QVariant FrameToValue(FramePtr frame) override;
|
||||
|
||||
virtual bool OutputIsShader(NodeOutput *output) override;
|
||||
virtual bool OutputIsAccelerated(NodeOutput *output) override;
|
||||
|
||||
virtual QVariant RunNodeAsShader(NodeOutput *output) override;
|
||||
virtual QVariant RunNodeAccelerated(NodeOutput *output) override;
|
||||
|
||||
virtual void TextureToBuffer(const QVariant& texture, QByteArray& buffer) override;
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@ RenderBackend::RenderBackend(QObject *parent) :
|
||||
started_(false),
|
||||
viewer_node_(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
RenderBackend::~RenderBackend()
|
||||
{
|
||||
}
|
||||
|
||||
bool RenderBackend::Init()
|
||||
@@ -35,6 +30,9 @@ bool RenderBackend::Init()
|
||||
|
||||
started_ = InitInternal();
|
||||
|
||||
// Connects workers and moves them to their respective threads
|
||||
InitWorkers();
|
||||
|
||||
if (!started_) {
|
||||
Close();
|
||||
}
|
||||
@@ -163,7 +161,7 @@ void RenderBackend::CacheNext()
|
||||
|
||||
TimeRange cache_frame = cache_queue_.takeFirst();
|
||||
|
||||
qDebug() << "Caching FRAME" << cache_frame.in() << "to" << cache_frame.out();
|
||||
//qDebug() << "Caching FRAME" << cache_frame.in() << "to" << cache_frame.out();
|
||||
|
||||
caching_ = GenerateData(cache_frame);
|
||||
}
|
||||
@@ -175,7 +173,7 @@ bool RenderBackend::GenerateData(const TimeRange &range)
|
||||
return false;
|
||||
}
|
||||
|
||||
NodeDependency dep = NodeDependency(viewer_node()->texture_input()->get_connected_output(), range.in(), range.out());
|
||||
NodeDependency dep = NodeDependency(GetDependentInput()->get_connected_output(), range.in(), range.out());
|
||||
|
||||
foreach (RenderWorker* worker, processors_) {
|
||||
if (worker->IsAvailable() || worker == processors_.last()) {
|
||||
@@ -214,3 +212,35 @@ void RenderBackend::CacheIDChangedEvent(const QString &id)
|
||||
{
|
||||
Q_UNUSED(id)
|
||||
}
|
||||
|
||||
void RenderBackend::InitWorkers()
|
||||
{
|
||||
for (int i=0;i<processors_.size();i++) {
|
||||
RenderWorker* processor = processors_.at(i);
|
||||
QThread* thread = threads().at(i);
|
||||
|
||||
// Connect to it
|
||||
connect(processor, SIGNAL(RequestSibling(NodeDependency)), this, SLOT(ThreadRequestedSibling(NodeDependency)));
|
||||
ConnectWorkerToThis(processor);
|
||||
|
||||
// Finally, we can move it to its own thread
|
||||
processor->moveToThread(thread);
|
||||
|
||||
// This function blocks the main thread intentionally. See the documentation for this function to see why.
|
||||
processor->Init();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::ThreadRequestedSibling(NodeDependency dep)
|
||||
{
|
||||
// Try to queue another thread to run this dep in advance
|
||||
foreach (RenderWorker* worker, processors_) {
|
||||
if (worker->IsAvailable()) {
|
||||
QMetaObject::invokeMethod(worker,
|
||||
"RenderAsSibling",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(NodeDependency, dep));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ class RenderBackend : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
RenderBackend(QObject* parent = nullptr);
|
||||
virtual ~RenderBackend() override;
|
||||
|
||||
Q_DISABLE_COPY_MOVE(RenderBackend)
|
||||
|
||||
@@ -68,6 +67,12 @@ protected:
|
||||
|
||||
bool GenerateData(const TimeRange& range);
|
||||
|
||||
void InitWorkers();
|
||||
|
||||
virtual NodeInput* GetDependentInput() = 0;
|
||||
|
||||
virtual void ConnectWorkerToThis(RenderWorker* worker) = 0;
|
||||
|
||||
ViewerOutput* viewer_node() const;
|
||||
|
||||
DecoderCache* decoder_cache();
|
||||
@@ -109,6 +114,9 @@ private:
|
||||
qint64 cache_time_;
|
||||
QString cache_id_;
|
||||
|
||||
private slots:
|
||||
void ThreadRequestedSibling(NodeDependency dep);
|
||||
|
||||
};
|
||||
|
||||
#endif // RENDERBACKEND_H
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "renderworker.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include "node/block/block.h"
|
||||
|
||||
RenderWorker::RenderWorker(DecoderCache *decoder_cache, QObject *parent) :
|
||||
@@ -50,6 +52,8 @@ void RenderWorker::Render(NodeDependency path)
|
||||
|
||||
RenderInternal(path);
|
||||
|
||||
emit CompletedCache(path);
|
||||
|
||||
// Unlock all Nodes so changes can be made again
|
||||
foreach (Node* dep, all_nodes_in_graph) {
|
||||
dep->UnlockUserInput();
|
||||
@@ -61,26 +65,47 @@ DecoderCache *RenderWorker::decoder_cache()
|
||||
return decoder_cache_;
|
||||
}
|
||||
|
||||
Node *RenderWorker::ValidateBlock(Node *n, const rational& time)
|
||||
Block *RenderWorker::ValidateBlock(Block *block, const rational& time)
|
||||
{
|
||||
if (n->IsBlock()) {
|
||||
Block* block = static_cast<Block*>(n);
|
||||
Q_ASSERT(block != nullptr && time >= 0);
|
||||
|
||||
while (block != nullptr && block->in() > time) {
|
||||
// This Block is too late, find an earlier one
|
||||
block = block->previous();
|
||||
}
|
||||
|
||||
while (block != nullptr && block->out() <= time) {
|
||||
// This block is too early, find a later one
|
||||
block = block->next();
|
||||
}
|
||||
|
||||
// By this point, we should have the correct Block or nullptr if there's no Block here
|
||||
return block;
|
||||
while (block->in() > time) {
|
||||
// This Block is too late, find an earlier one
|
||||
block = block->previous();
|
||||
}
|
||||
|
||||
return n;
|
||||
while (block->out() <= time) {
|
||||
// This block is too early, find a later one
|
||||
if (block->next() == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
block = block->next();
|
||||
}
|
||||
|
||||
// By this point, we should have the correct Block or nullptr if there's no Block here
|
||||
return block;
|
||||
}
|
||||
|
||||
QList<Block *> RenderWorker::ValidateBlockRange(Block *n, const TimeRange &range)
|
||||
{
|
||||
QList<Block*> list;
|
||||
Block* block_at_start = ValidateBlock(n, range.in());
|
||||
Block* block_at_end = ValidateBlock(n, range.out());
|
||||
|
||||
list.append(block_at_start);
|
||||
|
||||
// If more than one block is active for this range
|
||||
if (block_at_start != block_at_end) {
|
||||
|
||||
// Collect all blocks between the start and the end
|
||||
do {
|
||||
block_at_start = block_at_start->next();
|
||||
list.append(block_at_start);
|
||||
} while (block_at_start != block_at_end);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void RenderWorker::RenderInternal(const NodeDependency &path)
|
||||
@@ -123,3 +148,128 @@ bool RenderWorker::IsStarted()
|
||||
{
|
||||
return started_;
|
||||
}
|
||||
|
||||
QList<NodeInput*> RenderWorker::ProcessNodeInputsForTime(Node *n, const TimeRange &time)
|
||||
{
|
||||
QList<NodeInput*> connected_inputs;
|
||||
|
||||
// Now we need to gather information about this Node's inputs
|
||||
foreach (NodeParam* param, n->parameters()) {
|
||||
// Check if this parameter is an input and if the Node is dependent on it
|
||||
if (param->type() == NodeParam::kInput) {
|
||||
NodeInput* input = static_cast<NodeInput*>(param);
|
||||
|
||||
if (input->dependent()) {
|
||||
// If we're here, this input is necessary and we need to acquire the value for this Node
|
||||
if (input->IsConnected()) {
|
||||
// If it's connected to something, we need to retrieve that output at some point
|
||||
connected_inputs.append(input);
|
||||
} else {
|
||||
// If it isn't connected, it'll have the value we need inside it. We just need to store it for the node.
|
||||
input->set_stored_value(input->get_value_at_time(n->InputTimeAdjustment(input, time).in()));
|
||||
}
|
||||
|
||||
// Special types like FOOTAGE require extra work from us (to decrease node complexity dealing with decoders)
|
||||
if (input->data_type() == NodeParam::kFootage) {
|
||||
input->set_stored_value(0);
|
||||
|
||||
DecoderPtr decoder = ResolveDecoderFromInput(input);
|
||||
|
||||
// By this point we should definitely have a decoder, and if we don't something's gone terribly wrong
|
||||
if (decoder != nullptr) {
|
||||
FramePtr frame = decoder->Retrieve(time.in(), time.out() - time.in());
|
||||
|
||||
if (frame != nullptr) {
|
||||
QVariant value = FrameToValue(frame);
|
||||
|
||||
input->set_stored_value(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connected_inputs;
|
||||
}
|
||||
|
||||
QVariant RenderWorker::ProcessNodeNormally(const NodeDependency& dep)
|
||||
{
|
||||
NodeOutput* output = dep.node();
|
||||
Node* node = dep.node()->parent();
|
||||
|
||||
//qDebug() << "Processing" << node->id();
|
||||
|
||||
// Check if the output already has a value for this time
|
||||
if (output->has_cached_value(dep.range())) {
|
||||
// If so, we don't need to do anything, we can just send this value and exit here
|
||||
return output->get_cached_value(dep.range());
|
||||
}
|
||||
|
||||
// We need to run the Node's code to get the correct value for this time
|
||||
|
||||
QList<NodeInput*> connected_inputs = ProcessNodeInputsForTime(node, dep.range());
|
||||
|
||||
// For each connected input, we need to acquire the value from another node
|
||||
while (!connected_inputs.isEmpty()) {
|
||||
|
||||
// Remove any inputs from the list that we have valid cached values for already
|
||||
for (int i=0;i<connected_inputs.size();i++) {
|
||||
NodeInput* input = connected_inputs.at(i);
|
||||
NodeOutput* connected_output = input->get_connected_output();
|
||||
TimeRange input_time = node->InputTimeAdjustment(input, dep.range());
|
||||
|
||||
if (connected_output->has_cached_value(input_time)) {
|
||||
// This output already has this value, no need to process it again
|
||||
input->set_stored_value(connected_output->get_cached_value(input_time));
|
||||
connected_inputs.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// For every connected input except the first, we'll request another Node to do it
|
||||
int input_for_this_thread = -1;
|
||||
|
||||
for (int i=0;i<connected_inputs.size();i++) {
|
||||
NodeInput* input = connected_inputs.at(i);
|
||||
|
||||
// If this node is locked, we assume it's already being processed. Otherwise we need to request a sibling
|
||||
if (!input->get_connected_node()->IsProcessingLocked()) {
|
||||
if (input_for_this_thread == -1) {
|
||||
// Store this later since we can process it on this thread as we wait for other threads
|
||||
input_for_this_thread = i;
|
||||
} else {
|
||||
TimeRange input_time = node->InputTimeAdjustment(input, dep.range());
|
||||
|
||||
emit RequestSibling(NodeDependency(input->get_connected_output(),
|
||||
input_time));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (input_for_this_thread > -1) {
|
||||
// In the mean time, this thread can go off to do the first parameter
|
||||
NodeInput* input = connected_inputs.at(input_for_this_thread);
|
||||
TimeRange input_range = node->InputTimeAdjustment(input, dep.range());
|
||||
RenderAsSibling(NodeDependency(input->get_connected_output(),
|
||||
input_range));
|
||||
input->set_stored_value(input->get_connected_output()->get_cached_value(input_range));
|
||||
connected_inputs.removeAt(input_for_this_thread);
|
||||
} else {
|
||||
// Nothing for this thread to do. We'll wait 0.5 sec and check again for other nodes
|
||||
// FIXME: It would be nicer if this thread could do other nodes during this time
|
||||
QThread::msleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
// By this point, the node should have all the inputs it needs to render correctly
|
||||
|
||||
// Check if we have a shader for this output
|
||||
if (OutputIsAccelerated(output)) {
|
||||
// Run code
|
||||
return RunNodeAccelerated(output);
|
||||
} else {
|
||||
// Generate the value as expected
|
||||
return node->Value(output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "node/block/block.h"
|
||||
#include "node/node.h"
|
||||
#include "decodercache.h"
|
||||
|
||||
@@ -27,8 +28,36 @@ public slots:
|
||||
|
||||
virtual void RenderAsSibling(NodeDependency dep) = 0;
|
||||
|
||||
signals:
|
||||
void RequestSibling(NodeDependency path);
|
||||
|
||||
void CompletedCache(NodeDependency dep);
|
||||
|
||||
protected:
|
||||
Node* ValidateBlock(Node* n, const rational& time);
|
||||
/**
|
||||
* @brief Returns the block in a sequence that is active at a given time
|
||||
*
|
||||
* Blocks are connected to each other previous/next to create a BlockList or a sequence of blocks. The block that
|
||||
* is currently "active" depends on the time and only one block in a track can be active at any given time.
|
||||
*
|
||||
* Calling this function with a block and a time will traverse the provided block's track to find the block that will
|
||||
* be active at that time. The block must be valid (non-null) and the time must be valid (>= 0).
|
||||
*
|
||||
* This function may return the same block that it was called with. It will never return nullptr.
|
||||
*/
|
||||
Block *ValidateBlock(Block* block, const rational& time);
|
||||
|
||||
/**
|
||||
* @brief Returns all the blocks that could be active within a range of time
|
||||
*
|
||||
* Similar to ValidateBlock() but rather than returning one block for a single time, this function returns a list of
|
||||
* blocks that could be active within a range of time.
|
||||
*
|
||||
* The block must be valid (non-null) and the time must be valid (>= 0).
|
||||
*
|
||||
* The list will always contain at least one entry.
|
||||
*/
|
||||
QList<Block*> ValidateBlockRange(Block* n, const TimeRange& range);
|
||||
|
||||
virtual bool InitInternal() = 0;
|
||||
|
||||
@@ -36,11 +65,21 @@ protected:
|
||||
|
||||
virtual void RenderInternal(const NodeDependency& path);
|
||||
|
||||
virtual bool OutputIsAccelerated(NodeOutput *output) = 0;
|
||||
|
||||
virtual QVariant RunNodeAccelerated(NodeOutput *output) = 0;
|
||||
|
||||
StreamPtr ResolveStreamFromInput(NodeInput* input);
|
||||
DecoderPtr ResolveDecoderFromInput(NodeInput* input);
|
||||
|
||||
QList<Node*> ListNodeAndAllDependencies(Node* n);
|
||||
|
||||
QList<NodeInput*> ProcessNodeInputsForTime(Node* n, const TimeRange& time);
|
||||
|
||||
virtual QVariant FrameToValue(FramePtr frame) = 0;
|
||||
|
||||
QVariant ProcessNodeNormally(const NodeDependency &dep);
|
||||
|
||||
DecoderCache* decoder_cache();
|
||||
|
||||
QAtomicInt working_;
|
||||
|
||||
@@ -36,11 +36,6 @@ VideoRenderBackend::VideoRenderBackend(QObject *parent) :
|
||||
SetCacheName("Test");
|
||||
}
|
||||
|
||||
VideoRenderBackend::~VideoRenderBackend()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void VideoRenderBackend::InvalidateCache(const rational &start_range, const rational &end_range)
|
||||
{
|
||||
if (!params_.is_valid()) {
|
||||
@@ -51,10 +46,10 @@ void VideoRenderBackend::InvalidateCache(const rational &start_range, const rati
|
||||
rational start_range_adj = qMax(rational(0), start_range);
|
||||
rational end_range_adj = qMin(viewer_node()->Length(), end_range);
|
||||
|
||||
qDebug() << "Cache invalidated between"
|
||||
/*qDebug() << "Cache invalidated between"
|
||||
<< start_range_adj.toDouble()
|
||||
<< "and"
|
||||
<< end_range_adj.toDouble();
|
||||
<< end_range_adj.toDouble();*/
|
||||
|
||||
// Snap start_range to timebase
|
||||
double start_range_dbl = start_range_adj.toDouble();
|
||||
@@ -100,6 +95,9 @@ void VideoRenderBackend::InvalidateCache(const rational &start_range, const rati
|
||||
}
|
||||
}
|
||||
|
||||
// Remove frames after this time code if it's changed
|
||||
frame_cache_.Truncate(viewer_node()->Length());
|
||||
|
||||
CacheNext();
|
||||
}
|
||||
|
||||
@@ -160,6 +158,14 @@ void VideoRenderBackend::CacheIDChangedEvent(const QString &id)
|
||||
frame_cache_.SetCacheID(id);
|
||||
}
|
||||
|
||||
void VideoRenderBackend::ConnectWorkerToThis(RenderWorker *processor)
|
||||
{
|
||||
connect(processor, SIGNAL(CompletedFrame(NodeDependency, QByteArray)), this, SLOT(ThreadCompletedFrame(NodeDependency, QByteArray)));
|
||||
connect(processor, SIGNAL(HashAlreadyBeingCached()), this, SLOT(ThreadSkippedFrame()));
|
||||
connect(processor, SIGNAL(CompletedDownload(NodeDependency, QByteArray)), this, SLOT(ThreadCompletedDownload(NodeDependency, QByteArray)));
|
||||
connect(processor, SIGNAL(HashAlreadyExists(NodeDependency, QByteArray)), this, SLOT(ThreadHashAlreadyExists(NodeDependency, QByteArray)));
|
||||
}
|
||||
|
||||
VideoRenderFrameCache *VideoRenderBackend::frame_cache()
|
||||
{
|
||||
return &frame_cache_;
|
||||
@@ -207,3 +213,8 @@ const char *VideoRenderBackend::GetCachedFrame(const rational &time)
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeInput *VideoRenderBackend::GetDependentInput()
|
||||
{
|
||||
return viewer_node()->texture_input();
|
||||
}
|
||||
|
||||
@@ -44,8 +44,6 @@ public:
|
||||
*/
|
||||
VideoRenderBackend(QObject* parent = nullptr);
|
||||
|
||||
virtual ~VideoRenderBackend() override;
|
||||
|
||||
/**
|
||||
* @brief Set parameters of the Renderer
|
||||
*
|
||||
@@ -77,6 +75,8 @@ protected:
|
||||
|
||||
const char *GetCachedFrame(const rational& time);
|
||||
|
||||
virtual NodeInput* GetDependentInput() override;
|
||||
|
||||
VideoRenderFrameCache* frame_cache();
|
||||
|
||||
const VideoRenderingParams& params() const;
|
||||
@@ -88,6 +88,8 @@ protected:
|
||||
|
||||
virtual void CacheIDChangedEvent(const QString& id) override;
|
||||
|
||||
virtual void ConnectWorkerToThis(RenderWorker* processor) override;
|
||||
|
||||
signals:
|
||||
void CachedFrameReady(const rational& time);
|
||||
|
||||
|
||||
@@ -67,6 +67,19 @@ void VideoRenderFrameCache::RemoveHash(const rational &time)
|
||||
time_hash_map_.remove(time);
|
||||
}
|
||||
|
||||
void VideoRenderFrameCache::Truncate(const rational &time)
|
||||
{
|
||||
QMap<rational, QByteArray>::iterator i = time_hash_map_.begin();
|
||||
|
||||
while (i != time_hash_map_.end()) {
|
||||
if (i.key() >= time) {
|
||||
i = time_hash_map_.erase(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRenderFrameCache::RemoveHashFromCurrentlyCaching(const QByteArray &hash)
|
||||
{
|
||||
currently_caching_lock_.lock();
|
||||
|
||||
@@ -37,6 +37,8 @@ public:
|
||||
void SetHash(const rational& time, const QByteArray& hash);
|
||||
void RemoveHash(const rational& time);
|
||||
|
||||
void Truncate(const rational& time);
|
||||
|
||||
private:
|
||||
void RemoveHashFromCurrentlyCaching(const QByteArray& hash);
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "videorenderworker.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "node/node.h"
|
||||
#include "render/pixelservice.h"
|
||||
@@ -43,8 +41,8 @@ void VideoRenderWorker::RenderInternal(const NodeDependency& path)
|
||||
void VideoRenderWorker::HashNodeRecursively(QCryptographicHash *hash, Node* n, const rational& time)
|
||||
{
|
||||
// Resolve BlockList
|
||||
if (n->IsBlock() && (n = ValidateBlock(n, time)) == nullptr) {
|
||||
return;
|
||||
if (n->IsBlock()) {
|
||||
n = ValidateBlock(static_cast<Block*>(n), time);
|
||||
}
|
||||
|
||||
// Add this Node's ID
|
||||
@@ -55,42 +53,44 @@ void VideoRenderWorker::HashNodeRecursively(QCryptographicHash *hash, Node* n, c
|
||||
if (param->type() == NodeParam::kInput) {
|
||||
NodeInput* input = static_cast<NodeInput*>(param);
|
||||
|
||||
// Get time adjustment
|
||||
TimeRange range = n->InputTimeAdjustment(input, TimeRange(time, time));
|
||||
if (input->dependent()) {
|
||||
// Get time adjustment
|
||||
TimeRange range = n->InputTimeAdjustment(input, TimeRange(time, time));
|
||||
|
||||
// For a single frame, we only care about one of the times
|
||||
rational input_time = range.in();
|
||||
// For a single frame, we only care about one of the times
|
||||
rational input_time = range.in();
|
||||
|
||||
if (input->IsConnected()) {
|
||||
// Traverse down this edge
|
||||
HashNodeRecursively(hash, input->get_connected_node(), input_time);
|
||||
} else {
|
||||
// Grab the value at this time
|
||||
QVariant value = input->get_value_at_time(input_time);
|
||||
hash->addData(NodeParam::ValueToBytes(input->data_type(), value));
|
||||
}
|
||||
if (input->IsConnected()) {
|
||||
// Traverse down this edge
|
||||
HashNodeRecursively(hash, input->get_connected_node(), input_time);
|
||||
} else {
|
||||
// Grab the value at this time
|
||||
QVariant value = input->get_value_at_time(input_time);
|
||||
hash->addData(NodeParam::ValueToBytes(input->data_type(), value));
|
||||
}
|
||||
|
||||
// We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer
|
||||
if (input->data_type() == NodeParam::kFootage) {
|
||||
StreamPtr stream = ResolveStreamFromInput(input);
|
||||
DecoderPtr decoder = ResolveDecoderFromInput(input);
|
||||
// We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer
|
||||
if (input->data_type() == NodeParam::kFootage) {
|
||||
StreamPtr stream = ResolveStreamFromInput(input);
|
||||
DecoderPtr decoder = ResolveDecoderFromInput(input);
|
||||
|
||||
if (decoder != nullptr) {
|
||||
// Add footage details to hash
|
||||
if (decoder != nullptr) {
|
||||
// Add footage details to hash
|
||||
|
||||
// Footage filename
|
||||
hash->addData(stream->footage()->filename().toUtf8());
|
||||
// Footage filename
|
||||
hash->addData(stream->footage()->filename().toUtf8());
|
||||
|
||||
// Footage last modified date
|
||||
hash->addData(stream->footage()->timestamp().toString().toUtf8());
|
||||
// Footage last modified date
|
||||
hash->addData(stream->footage()->timestamp().toString().toUtf8());
|
||||
|
||||
// Footage stream
|
||||
hash->addData(QString::number(stream->index()).toUtf8());
|
||||
// Footage stream
|
||||
hash->addData(QString::number(stream->index()).toUtf8());
|
||||
|
||||
// Footage timestamp
|
||||
hash->addData(QString::number(decoder->GetTimestampFromTime(time)).toUtf8());
|
||||
// Footage timestamp
|
||||
hash->addData(QString::number(decoder->GetTimestampFromTime(time)).toUtf8());
|
||||
|
||||
// FIXME: Add colorspace and alpha assoc
|
||||
// FIXME: Add colorspace and alpha assoc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,165 +115,40 @@ void VideoRenderWorker::CloseInternal()
|
||||
download_buffer_.clear();
|
||||
}
|
||||
|
||||
QList<NodeInput*> VideoRenderWorker::ProcessNodeInputsForTime(Node *n, const TimeRange &time)
|
||||
{
|
||||
QList<NodeInput*> connected_inputs;
|
||||
|
||||
// Now we need to gather information about this Node's inputs
|
||||
foreach (NodeParam* param, n->parameters()) {
|
||||
// Check if this parameter is an input and if the Node is dependent on it
|
||||
if (param->type() == NodeParam::kInput) {
|
||||
NodeInput* input = static_cast<NodeInput*>(param);
|
||||
|
||||
if (input->dependent()) {
|
||||
// If we're here, this input is necessary and we need to acquire the value for this Node
|
||||
if (input->IsConnected()) {
|
||||
// If it's connected to something, we need to retrieve that output at some point
|
||||
connected_inputs.append(input);
|
||||
} else {
|
||||
// If it isn't connected, it'll have the value we need inside it. We just need to store it for the node.
|
||||
input->set_stored_value(input->get_value_at_time(n->InputTimeAdjustment(input, time).in()));
|
||||
}
|
||||
|
||||
// Special types like FOOTAGE require extra work from us (to decrease node complexity dealing with decoders)
|
||||
if (input->data_type() == NodeParam::kFootage) {
|
||||
input->set_stored_value(0);
|
||||
|
||||
DecoderPtr decoder = ResolveDecoderFromInput(input);
|
||||
|
||||
// By this point we should definitely have a decoder, and if we don't something's gone terribly wrong
|
||||
if (decoder != nullptr) {
|
||||
FramePtr frame = decoder->Retrieve(time.in());
|
||||
|
||||
if (frame != nullptr) {
|
||||
QVariant value = FrameToTexture(frame);
|
||||
|
||||
input->set_stored_value(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connected_inputs;
|
||||
}
|
||||
|
||||
void VideoRenderWorker::RenderAsSibling(NodeDependency dep)
|
||||
{
|
||||
NodeOutput* output = dep.node();
|
||||
Node* original_node = output->parent();
|
||||
Node* node;
|
||||
rational time = dep.in();
|
||||
QList<NodeInput*> connected_inputs;
|
||||
QVariant value;
|
||||
|
||||
// Set working state
|
||||
working_++;
|
||||
|
||||
qDebug() << "Processing" << original_node->id() << original_node;
|
||||
//qDebug() << "Processing" << original_node->id() << original_node;
|
||||
|
||||
original_node->LockProcessing();
|
||||
|
||||
// Firstly we check if this node is a "Block", if it is that means it's part of a linked list of mutually exclusive
|
||||
// nodes based on time and we might need to locate which Block to attach to
|
||||
if ((node = ValidateBlock(original_node, time)) == nullptr) {
|
||||
// ValidateBlock() may have returned nullptr if there was no Block found at this time so no texture to return
|
||||
output->cache_value(dep.range(), 0);
|
||||
if (original_node->IsBlock()) {
|
||||
node = ValidateBlock(static_cast<Block*>(original_node), time);
|
||||
|
||||
original_node->UnlockProcessing();
|
||||
goto end_render;
|
||||
}
|
||||
if (original_node != node) {
|
||||
// Ensure output is the output matching the node as it may have changed
|
||||
output = static_cast<NodeOutput*>(node->GetParameterWithID(output->id()));
|
||||
|
||||
if (original_node != node) {
|
||||
// Ensure output is the output matching the node as it may have changed
|
||||
output = static_cast<NodeOutput*>(node->GetParameterWithID(output->id()));
|
||||
|
||||
// Switch locks
|
||||
original_node->UnlockProcessing();
|
||||
node->LockProcessing();
|
||||
|
||||
qDebug() << "Deftly switched from" << original_node->id() << original_node << "to" << node->id() << node;
|
||||
}
|
||||
|
||||
// Check if the output already has a value for this time
|
||||
if (output->has_cached_value(dep.range())) {
|
||||
// If so, we don't need to do anything, we can just send this value and exit here
|
||||
dep.node()->cache_value(dep.range(), output->get_cached_value(dep.range()));
|
||||
|
||||
qDebug() << "Found a cached value on" << node->id() << output->id() << "at" << dep.range().in() << "-" << dep.range().out();
|
||||
|
||||
node->UnlockProcessing();
|
||||
goto end_render;
|
||||
}
|
||||
|
||||
// We need to run the Node's code to get the correct value for this time
|
||||
|
||||
connected_inputs = ProcessNodeInputsForTime(node, dep.range());
|
||||
|
||||
// For each connected input, we need to acquire the value from another node
|
||||
while (!connected_inputs.isEmpty()) {
|
||||
|
||||
// Remove any inputs from the list that we have valid cached values for already
|
||||
for (int i=0;i<connected_inputs.size();i++) {
|
||||
NodeInput* input = connected_inputs.at(i);
|
||||
NodeOutput* connected_output = input->get_connected_output();
|
||||
TimeRange input_time = node->InputTimeAdjustment(input, dep.range());
|
||||
|
||||
if (connected_output->has_cached_value(input_time)) {
|
||||
// This output already has this value, no need to process it again
|
||||
input->set_stored_value(connected_output->get_cached_value(input_time));
|
||||
connected_inputs.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
// Switch locks
|
||||
original_node->UnlockProcessing();
|
||||
node->LockProcessing();
|
||||
}
|
||||
|
||||
// For every connected input except the first, we'll request another Node to do it
|
||||
int input_for_this_thread = -1;
|
||||
|
||||
for (int i=0;i<connected_inputs.size();i++) {
|
||||
NodeInput* input = connected_inputs.at(i);
|
||||
|
||||
// If this node is locked, we assume it's already being processed. Otherwise we need to request a sibling
|
||||
if (!input->get_connected_node()->IsProcessingLocked()) {
|
||||
if (input_for_this_thread == -1) {
|
||||
// Store this later since we can process it on this thread as we wait for other threads
|
||||
input_for_this_thread = i;
|
||||
} else {
|
||||
TimeRange input_time = node->InputTimeAdjustment(input, dep.range());
|
||||
|
||||
emit RequestSibling(NodeDependency(input->get_connected_output(),
|
||||
input_time));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (input_for_this_thread > -1) {
|
||||
// In the mean time, this thread can go off to do the first parameter
|
||||
NodeInput* input = connected_inputs.at(input_for_this_thread);
|
||||
TimeRange input_range = node->InputTimeAdjustment(input, dep.range());
|
||||
RenderAsSibling(NodeDependency(input->get_connected_output(),
|
||||
input_range));
|
||||
input->set_stored_value(input->get_connected_output()->get_cached_value(input_range));
|
||||
connected_inputs.removeAt(input_for_this_thread);
|
||||
} else {
|
||||
// Nothing for this thread to do. We'll wait 0.5 sec and check again for other nodes
|
||||
// FIXME: It would be nicer if this thread could do other nodes during this time
|
||||
QThread::msleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
// By this point, the node should have all the inputs it needs to render correctly
|
||||
|
||||
// Check if we have a shader for this output
|
||||
if (OutputIsShader(output)) {
|
||||
// Run code
|
||||
value = RunNodeAsShader(output);
|
||||
} else {
|
||||
// Generate the value as expected
|
||||
value = node->Value(output);
|
||||
node = original_node;
|
||||
}
|
||||
|
||||
value = ProcessNodeNormally(NodeDependency(output, dep.range()));
|
||||
|
||||
// Place the value into the output
|
||||
output->cache_value(dep.range(), value);
|
||||
dep.node()->cache_value(dep.range(), value);
|
||||
@@ -281,13 +156,10 @@ void VideoRenderWorker::RenderAsSibling(NodeDependency dep)
|
||||
// We're done!
|
||||
node->UnlockProcessing();
|
||||
|
||||
end_render:
|
||||
// End this working state
|
||||
working_--;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void VideoRenderWorker::Download(NodeDependency dep, QByteArray hash, QVariant texture, QString filename)
|
||||
{
|
||||
working_++;
|
||||
@@ -305,7 +177,7 @@ void VideoRenderWorker::Download(NodeDependency dep, QByteArray hash, QVariant t
|
||||
std::unique_ptr<OIIO::ImageOutput> out = OIIO::ImageOutput::create(working_fn_std);
|
||||
|
||||
if (out) {
|
||||
qDebug() << "Saving to" << filename;
|
||||
//qDebug() << "Saving to" << filename;
|
||||
out->open(working_fn_std, spec);
|
||||
out->write_image(format_info.oiio_desc, download_buffer_.data());
|
||||
out->close();
|
||||
|
||||
@@ -21,8 +21,6 @@ public slots:
|
||||
void Download(NodeDependency dep, QByteArray hash, QVariant texture, QString filename);
|
||||
|
||||
signals:
|
||||
void RequestSibling(NodeDependency path);
|
||||
|
||||
void CompletedFrame(NodeDependency path, QByteArray hash);
|
||||
|
||||
void CompletedDownload(NodeDependency path, QByteArray hash);
|
||||
@@ -36,16 +34,10 @@ protected:
|
||||
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
virtual QVariant FrameToTexture(FramePtr frame) = 0;
|
||||
|
||||
const VideoRenderingParams& video_params();
|
||||
|
||||
virtual void ParametersChangedEvent(){}
|
||||
|
||||
virtual bool OutputIsShader(NodeOutput *output) = 0;
|
||||
|
||||
virtual QVariant RunNodeAsShader(NodeOutput *output) = 0;
|
||||
|
||||
virtual void TextureToBuffer(const QVariant& texture, QByteArray& buffer) = 0;
|
||||
|
||||
virtual void RenderInternal(const NodeDependency& path) override;
|
||||
@@ -53,8 +45,6 @@ protected:
|
||||
private:
|
||||
void ProcessNode();
|
||||
|
||||
QList<NodeInput*> ProcessNodeInputsForTime(Node* n, const TimeRange& time);
|
||||
|
||||
void HashNodeRecursively(QCryptographicHash* hash, Node *n, const rational &time);
|
||||
|
||||
VideoRenderingParams video_params_;
|
||||
|
||||
@@ -122,7 +122,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm
|
||||
switch (static_cast<olive::PixelFormat>(frame->format())) {
|
||||
case olive::PIX_FMT_RGBA8:
|
||||
{
|
||||
uint8_t* source = frame->data();
|
||||
uint8_t* source = reinterpret_cast<uint8_t*>(frame->data());
|
||||
|
||||
switch (dest_format) {
|
||||
case olive::PIX_FMT_RGBA16U: // 8-bit Integer -> 16-bit Integer
|
||||
@@ -163,7 +163,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm
|
||||
switch (dest_format) {
|
||||
case olive::PIX_FMT_RGBA8: // 16-bit Integer -> 8-bit Integer
|
||||
{
|
||||
uint8_t* destination = converted->data();
|
||||
uint8_t* destination = reinterpret_cast<uint8_t*>(converted->data());
|
||||
for (int i=0;i<pix_count;i++) {
|
||||
destination[i] = static_cast<uint8_t>(source[i] / 257);
|
||||
}
|
||||
@@ -199,7 +199,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm
|
||||
switch (dest_format) {
|
||||
case olive::PIX_FMT_RGBA8: // 16-bit Float -> 8-bit Integer
|
||||
{
|
||||
uint8_t* destination = converted->data();
|
||||
uint8_t* destination = reinterpret_cast<uint8_t*>(converted->data());
|
||||
for (int i=0;i<pix_count;i++) {
|
||||
destination[i] = static_cast<uint8_t>(source[i] * 255.0f);
|
||||
}
|
||||
@@ -235,7 +235,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm
|
||||
switch (dest_format) {
|
||||
case olive::PIX_FMT_RGBA8: // 32-bit Float -> 8-bit Integer
|
||||
{
|
||||
uint8_t* destination = converted->data();
|
||||
uint8_t* destination = reinterpret_cast<uint8_t*>(converted->data());
|
||||
for (int i=0;i<pix_count;i++) {
|
||||
destination[i] = static_cast<uint8_t>(source[i] * 255.0f);
|
||||
}
|
||||
@@ -293,7 +293,7 @@ void PixelService::ConvertRGBtoRGBA(FramePtr frame)
|
||||
while (rgb_iter >= 0) {
|
||||
memcpy(&frame->data()[rgba_iter], &frame->data()[rgb_iter], static_cast<size_t>(rgb_pixel_size));
|
||||
|
||||
uint8_t* alpha_ptr = &frame->data()[rgba_iter + rgb_pixel_size];
|
||||
uint8_t* alpha_ptr = reinterpret_cast<uint8_t*>(frame->data()) + rgba_iter + rgb_pixel_size;
|
||||
|
||||
// Write a full alpha value according to the format
|
||||
switch (dest_format) {
|
||||
|
||||
Reference in New Issue
Block a user