a first attempt at a new render system

This commit is contained in:
itsmattkc
2020-05-14 14:43:03 +10:00
parent c5dfddff0d
commit 863ae1b913
60 changed files with 927 additions and 3164 deletions
+6
View File
@@ -21,6 +21,8 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
render/audioparams.h
render/audioparams.cpp
render/audioplaybackcache.h
render/audioplaybackcache.cpp
render/color.h
render/color.cpp
render/colormanager.h
@@ -29,10 +31,14 @@ set(OLIVE_SOURCES
render/colorprocessor.cpp
render/diskmanager.h
render/diskmanager.cpp
render/framehashcache.h
render/framehashcache.cpp
render/managedcolor.h
render/managedcolor.cpp
render/pixelformat.h
render/pixelformat.cpp
render/playbackcache.h
render/playbackcache.cpp
render/rendermodes.h
render/videoparams.h
render/videoparams.cpp
+56
View File
@@ -0,0 +1,56 @@
/***
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 "audioplaybackcache.h"
#include <QDir>
#include <QRandomGenerator>
#include "common/filefunctions.h"
OLIVE_NAMESPACE_ENTER
AudioPlaybackCache::AudioPlaybackCache()
{
// FIXME: We just use a randomly generated number, one day this should be matchable to the
// project so it can be reused.
quint32 r = QRandomGenerator::global()->generate();
filename_ = QDir(FileFunctions::GetMediaCacheLocation()).filePath(QString::number(r));
filename_.append(QStringLiteral(".pcm"));
}
void AudioPlaybackCache::SetParameters(const AudioRenderingParams &params)
{
if (params_ == params) {
return;
}
// Our current audio cache is unusable, so we truncate it automatically
InvalidateAll();
emit ParametersChanged();
}
const QString &AudioPlaybackCache::GetCacheFilename() const
{
return filename_;
}
OLIVE_NAMESPACE_EXIT
@@ -18,35 +18,41 @@
***/
#ifndef AUDIOBACKEND_H
#define AUDIOBACKEND_H
#ifndef AUDIOPLAYBACKCACHE_H
#define AUDIOPLAYBACKCACHE_H
#include <QFile>
#include "../audiorenderbackend.h"
#include "common/timerange.h"
#include "codec/samplebuffer.h"
#include "render/playbackcache.h"
OLIVE_NAMESPACE_ENTER
class AudioBackend : public AudioRenderBackend
class AudioPlaybackCache : public PlaybackCache
{
Q_OBJECT
public:
AudioBackend(QObject* parent = nullptr);
AudioPlaybackCache();
virtual ~AudioBackend() override;
const AudioRenderingParams& GetParameters() const {
return params_;
}
protected:
virtual bool InitInternal() override;
void SetParameters(const AudioRenderingParams& params);
virtual void CloseInternal() override;
void WritePCM(SampleBufferPtr samples);
virtual void ConnectWorkerToThis(RenderWorker* worker) override;
const QString& GetCacheFilename() const;
private slots:
void ThreadCompletedCache(NodeDependency dep, NodeValueTable data, qint64 job_time);
signals:
void ParametersChanged();
private:
QString filename_;
AudioRenderingParams params_;
};
OLIVE_NAMESPACE_EXIT
#endif // AUDIOBACKEND_H
#endif // AUDIOPLAYBACKCACHE_H
-15
View File
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(audio)
add_subdirectory(opengl)
set(OLIVE_SOURCES
@@ -27,20 +26,6 @@ set(OLIVE_SOURCES
render/backend/renderbackend.h
render/backend/renderbackend.cpp
render/backend/renderworker.h
render/backend/renderworker.cpp
render/backend/audiorenderbackend.h
render/backend/audiorenderbackend.cpp
render/backend/audiorenderworker.h
render/backend/audiorenderworker.cpp
render/backend/videorenderbackend.h
render/backend/videorenderbackend.cpp
render/backend/videorenderframecache.h
render/backend/videorenderframecache.cpp
render/backend/videorenderworker.h
render/backend/videorenderworker.cpp
render/backend/rendercache.h
render/backend/colorprocessorcache.h
-24
View File
@@ -1,24 +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/>.
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
)
-116
View File
@@ -1,116 +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 "audiobackend.h"
#include "audioworker.h"
OLIVE_NAMESPACE_ENTER
AudioBackend::AudioBackend(QObject *parent) :
AudioRenderBackend(parent)
{
}
AudioBackend::~AudioBackend()
{
Close();
}
bool AudioBackend::InitInternal()
{
// 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(&node_copy_map_);
processor->SetParameters(params());
processors_.append(processor);
}
return true;
}
void AudioBackend::CloseInternal()
{
}
void AudioBackend::ConnectWorkerToThis(RenderWorker *worker)
{
AudioRenderBackend::ConnectWorkerToThis(worker);
connect(worker, &RenderWorker::CompletedCache, this, &AudioBackend::ThreadCompletedCache);
}
void AudioBackend::ThreadCompletedCache(NodeDependency dep, NodeValueTable data, qint64 job_time)
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
if (job_time == render_job_info_.value(dep.range())) {
render_job_info_.remove(dep.range());
SampleBufferPtr cached_sample_ptr = data.Get(NodeParam::kSamples).value<SampleBufferPtr>();
if (cached_sample_ptr) {
QByteArray cached_samples = cached_sample_ptr->toPackedData();
int offset = params().time_to_bytes(dep.in());
int length = params().time_to_bytes(dep.range().length());
int out_point = qMin(offset + length, params().time_to_bytes(GetSequenceLength()));
if (offset < out_point) {
if (offset + length > out_point) {
length = out_point - offset;
}
QFile f(CachePathName());
if (f.open(QFile::ReadWrite)) {
if (f.size() < out_point && !f.resize(out_point)) {
qCritical() << "Failed to resize file" << CachePathName();
}
if (!f.seek(offset)) {
qCritical() << "Failed to seek file" << CachePathName();
}
// Replace data with this data
int copy_length = qMin(length, cached_samples.size());
f.write(cached_samples.data(), copy_length);
if (copy_length < length) {
// Fill in remainder with silence
QByteArray empty_space(length - copy_length, 0);
f.write(empty_space);
}
f.close();
} else {
qWarning() << "Failed to write to cached PCM file";
}
}
}
}
CacheNext();
}
OLIVE_NAMESPACE_EXIT
-105
View File
@@ -1,105 +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 "audioworker.h"
OLIVE_NAMESPACE_ENTER
AudioWorker::AudioWorker(QHash<Node *, Node *> *copy_map, QObject *parent) :
AudioRenderWorker(copy_map, parent)
{
}
NodeValue AudioWorker::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range)
{
if (decoder->HasConformedVersion(audio_params())) {
SampleBufferPtr frame = decoder->RetrieveAudio(range.in(), range.out() - range.in(), audio_params());
if (frame) {
return NodeValue(NodeParam::kSamples, QVariant::fromValue(frame));
}
} else {
emit ConformUnavailable(decoder->stream(), CurrentPath().range(), range.out(), audio_params());
}
return NodeValue();
}
void AudioWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params_in, NodeValueTable &output_params)
{
// Check if node processes samples
if (!(node->GetCapabilities(input_params_in) & Node::kSampleProcessor)) {
return;
}
// Copy database so we can make some temporary modifications to it
NodeValueDatabase input_params = input_params_in;
NodeInput* sample_input = node->ProcessesSamplesFrom(input_params);
// Try to find the sample buffer in the table
QVariant samples_var = input_params[sample_input].Get(NodeParam::kSamples);
// If there isn't one, there's nothing to do
if (samples_var.isNull()) {
return;
}
SampleBufferPtr input_buffer = samples_var.value<SampleBufferPtr>();
if (!input_buffer) {
return;
}
SampleBufferPtr output_buffer = SampleBuffer::CreateAllocated(input_buffer->audio_params(), input_buffer->sample_count_per_channel());
int sample_count = input_buffer->sample_count_per_channel();
// FIXME: Hardcoded float sample format
for (int i=0;i<sample_count;i++) {
// Calculate the exact rational time at this sample
int sample_out_of_channel = i / audio_params().channel_count();
double sample_to_second = static_cast<double>(sample_out_of_channel) / static_cast<double>(audio_params().sample_rate());
rational this_sample_time = rational::fromDouble(range.in().toDouble() + sample_to_second);
// Update all non-sample and non-footage inputs
foreach (NodeParam* param, node->parameters()) {
if (param->type() == NodeParam::kInput
&& param != sample_input) {
NodeInput* input = static_cast<NodeInput*>(param);
// If the input isn't keyframing, we don't need to update it unless it's connected, in which case it may change
if (input->IsConnected() || input->is_keyframing()) {
input_params.Insert(input, ProcessInput(input, TimeRange(this_sample_time, this_sample_time)));
}
}
}
node->ProcessSamples(input_params,
audio_params(),
input_buffer,
output_buffer,
i);
}
output_params.Push(NodeParam::kSamples, QVariant::fromValue(output_buffer));
}
OLIVE_NAMESPACE_EXIT
-275
View File
@@ -1,275 +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 "audiorenderbackend.h"
#include <QDir>
#include <QtMath>
#include "audiorenderworker.h"
#include "common/filefunctions.h"
#include "task/conform/conform.h"
#include "task/taskmanager.h"
OLIVE_NAMESPACE_ENTER
AudioRenderBackend::AudioRenderBackend(QObject *parent) :
RenderBackend(parent),
ic_from_conform_(false)
{
connect(this, &AudioRenderBackend::QueueComplete, this, &AudioRenderBackend::FilterQueueCompleteSignal);
}
void AudioRenderBackend::SetParameters(const AudioRenderingParams &params)
{
CancelQueue();
// Set new parameters
params_ = params;
// Set params on all processors
foreach (RenderWorker* worker, processors_) {
static_cast<AudioRenderWorker*>(worker)->SetParameters(params_);
}
// Regenerate the cache ID
RegenerateCacheID();
emit ParamsChanged();
}
void AudioRenderBackend::ConnectViewer(ViewerOutput *node)
{
connect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache);
connect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache);
}
void AudioRenderBackend::DisconnectViewer(ViewerOutput *node)
{
disconnect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache);
disconnect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache);
conform_wait_info_.clear();
}
bool AudioRenderBackend::GenerateCacheIDInternal(QCryptographicHash &hash)
{
if (!params_.is_valid()) {
return false;
}
// Generate an ID that is more or less guaranteed to be unique to this Sequence
hash.addData(QString::number(params_.sample_rate()).toUtf8());
hash.addData(QString::number(params_.channel_layout()).toUtf8());
hash.addData(QString::number(params_.format()).toUtf8());
return true;
}
const AudioRenderingParams &AudioRenderBackend::params() const
{
return params_;
}
NodeInput *AudioRenderBackend::GetDependentInput()
{
return viewer_node()->samples_input();
}
QString AudioRenderBackend::CachePathName() const
{
QString cache_fn = cache_id();
cache_fn.append(".pcm");
return QDir(FileFunctions::GetMediaCacheLocation()).filePath(cache_fn);
}
bool AudioRenderBackend::CanRender()
{
return params_.is_valid();
}
void AudioRenderBackend::ConnectWorkerToThis(RenderWorker *worker)
{
AudioRenderWorker* arw = static_cast<AudioRenderWorker*>(worker);
connect(arw, &AudioRenderWorker::ConformUnavailable, this, &AudioRenderBackend::ConformUnavailable, Qt::QueuedConnection);
}
TimeRange AudioRenderBackend::PopNextFrameFromQueue()
{
TimeRange range = cache_queue_.first();
// Limit range per worker to 2 seconds (FIXME: arbitrary, should be tweaked, maybe even in config?)
range.set_out(qMin(range.out(), range.in() + rational(2)));
cache_queue_.RemoveTimeRange(range);
return range;
}
void AudioRenderBackend::InvalidateCacheInternal(const rational &start_range, const rational &end_range)
{
if (!ic_from_conform_) {
// Cancel any ranges waiting on a conform here since obviously the contents have changed
TimeRange range(start_range, end_range);
for (int i=0;i<conform_wait_info_.size();i++) {
ConformWaitInfo& info = conform_wait_info_[i];
// FIXME: Code copied from TimeRangeList::RemoveTimeRange()
if (range.Contains(info.affected_range)) {
conform_wait_info_.removeAt(i);
i--;
} else if (info.affected_range.Contains(range, false, false)) {
ConformWaitInfo copy = info;
info.affected_range.set_out(start_range);
copy.affected_range.set_in(end_range);
conform_wait_info_.append(copy);
} else if (info.affected_range.in() < start_range && info.affected_range.out() > start_range) {
info.affected_range.set_out(start_range);
} else if (info.affected_range.in() < end_range && info.affected_range.out() > end_range) {
info.affected_range.set_in(end_range);
}
}
}
RenderBackend::InvalidateCacheInternal(start_range, end_range);
}
void AudioRenderBackend::ListenForConformSignal(AudioStreamPtr s)
{
foreach (const ConformWaitInfo& info, conform_wait_info_) {
if (info.stream == s) {
// We've probably already connected to this one
return;
}
}
connect(s.get(), &AudioStream::ConformAppended, this, &AudioRenderBackend::ConformUpdated);
}
void AudioRenderBackend::StopListeningForConformSignal(AudioStream* s)
{
foreach (const ConformWaitInfo& info, conform_wait_info_) {
if (info.stream.get() == s) {
// There are still conforms we're waiting for, don't disconnect
return;
}
}
disconnect(s, &AudioStream::ConformAppended, this, &AudioRenderBackend::ConformUpdated);
}
void AudioRenderBackend::ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params)
{
ConformWaitInfo info = {stream, params, range, stream_time};
if (conform_wait_info_.contains(info)) {
return;
}
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(stream);
if (audio_stream->try_start_conforming(params)) {
// Start indexing process
ListenForConformSignal(audio_stream);
conform_wait_info_.append(info);
ConformTask* conform_task = new ConformTask(audio_stream, params);
TaskManager::instance()->AddTask(conform_task);
} else if (audio_stream->has_conformed_version(params)) {
// Conform JUST finished, requeue this time
ic_from_conform_ = true;
InvalidateCache(range, nullptr);
ic_from_conform_ = false;
} else {
// A conform task is already running, so we'll just wait for it
ListenForConformSignal(audio_stream);
conform_wait_info_.append(info);
}
}
void AudioRenderBackend::ConformUpdated(AudioRenderingParams params)
{
AudioStream *stream = static_cast<AudioStream*>(sender());
for (int i=0;i<conform_wait_info_.size();i++) {
const ConformWaitInfo& info = conform_wait_info_.at(i);
if (info.stream.get() == stream
&& info.params == params) {
// Make a copy so the values we use aren't corrupt
ConformWaitInfo copy = info;
// Remove this entry from the list
conform_wait_info_.removeAt(i);
i--;
// Send invalidate cache signal
ic_from_conform_ = true;
InvalidateCache(copy.affected_range, nullptr);
ic_from_conform_ = false;
}
}
StopListeningForConformSignal(stream);
}
void AudioRenderBackend::TruncateCache(const rational &r)
{
int seq_length = params_.time_to_bytes(r);
QFile cache_pcm(CachePathName());
if (cache_pcm.size() > seq_length) {
cache_pcm.resize(seq_length);
}
}
void AudioRenderBackend::FilterQueueCompleteSignal()
{
if (conform_wait_info_.isEmpty()) {
emit AudioComplete();
}
}
bool AudioRenderBackend::ConformWaitInfo::operator==(const AudioRenderBackend::ConformWaitInfo &rhs) const
{
return rhs.params == params
&& rhs.stream == stream
&& rhs.stream_time == stream_time
&& rhs.affected_range == affected_range;
}
OLIVE_NAMESPACE_EXIT
-105
View File
@@ -1,105 +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 AUDIORENDERBACKEND_H
#define AUDIORENDERBACKEND_H
#include "common/timerange.h"
#include "renderbackend.h"
OLIVE_NAMESPACE_ENTER
class AudioRenderBackend : public RenderBackend
{
Q_OBJECT
public:
AudioRenderBackend(QObject* parent = nullptr);
/**
* @brief Set parameters of the Renderer
*
* The Renderer owns the buffers that are used in the rendering process and this function sets the kind of buffers
* to use. The Renderer must be stopped when calling this function.
*/
void SetParameters(const AudioRenderingParams &params);
const AudioRenderingParams& params() const;
QString CachePathName() const;
signals:
void ParamsChanged();
void AudioComplete();
protected:
virtual void ConnectViewer(ViewerOutput* node) override;
virtual void DisconnectViewer(ViewerOutput* node) override;
/**
* @brief Internal function for generating the cache ID
*/
virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) override;
virtual NodeInput* GetDependentInput() override;
virtual bool CanRender() override;
virtual void ConnectWorkerToThis(RenderWorker* worker) override;
virtual TimeRange PopNextFrameFromQueue() override;
virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range) override;
private:
struct ConformWaitInfo {
StreamPtr stream;
AudioRenderingParams params;
TimeRange affected_range;
rational stream_time;
bool operator==(const ConformWaitInfo& rhs) const;
};
void ListenForConformSignal(AudioStreamPtr s);
void StopListeningForConformSignal(AudioStream *s);
QList<ConformWaitInfo> conform_wait_info_;
AudioRenderingParams params_;
bool ic_from_conform_;
private slots:
void ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params);
void ConformUpdated(OLIVE_NAMESPACE::AudioRenderingParams params);
void TruncateCache(const rational& r);
void FilterQueueCompleteSignal();
};
OLIVE_NAMESPACE_EXIT
#endif // AUDIORENDERBACKEND_H
-173
View File
@@ -1,173 +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 "audiorenderworker.h"
#include <QDir>
#include <QFloat16>
#include "audio/audiomanager.h"
#include "audio/sumsamples.h"
#include "config/config.h"
#include "node/block/clip/clip.h"
OLIVE_NAMESPACE_ENTER
AudioRenderWorker::AudioRenderWorker(QHash<Node *, Node *> *copy_map, QObject *parent) :
RenderWorker(parent),
copy_map_(copy_map)
{
}
void AudioRenderWorker::SetParameters(const AudioRenderingParams &audio_params)
{
audio_params_ = audio_params;
}
bool AudioRenderWorker::InitInternal()
{
// Nothing to init yet
return true;
}
void AudioRenderWorker::CloseInternal()
{
// Nothing to init yet
}
NodeValueTable AudioRenderWorker::RenderBlock(const TrackOutput *track, const TimeRange &range)
{
QList<Block*> active_blocks = track->BlocksAtTimeRange(range);
// All these blocks will need to output to a buffer so we create one here
SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params_, audio_params_.time_to_samples(range.length()));
block_range_buffer->fill(0);
NodeValueTable merged_table;
// Loop through active blocks retrieving their audio
foreach (Block* b, active_blocks) {
TimeRange range_for_block(qMax(b->in(), range.in()),
qMin(b->out(), range.out()));
int destination_offset = audio_params_.time_to_samples(range_for_block.in() - range.in());
int max_dest_sz = audio_params_.time_to_samples(range_for_block.length());
// Destination buffer
NodeValueTable table = ProcessNode(NodeDependency(b, range_for_block));
QVariant sample_val = table.Take(NodeParam::kSamples);
SampleBufferPtr samples_from_this_block;
if (sample_val.isNull()
|| !(samples_from_this_block = sample_val.value<SampleBufferPtr>())) {
// If we retrieved no samples from this block, do nothing
continue;
}
// Stretch samples here
rational abs_speed = qAbs(b->speed());
if (abs_speed != 1) {
samples_from_this_block->speed(abs_speed.toDouble());
}
if (b->is_reversed()) {
// Reverse the audio buffer
samples_from_this_block->reverse();
}
int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count_per_channel());
// Copy samples into destination buffer
block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length);
{
// Save waveform to file
Block* src_block = static_cast<Block*>(copy_map_->key(b));
QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
QDir waveform_loc = local_appdata_dir.filePath(QStringLiteral("waveform"));
waveform_loc.mkpath(".");
QString wave_fn(waveform_loc.filePath(QString::number(reinterpret_cast<quintptr>(src_block))));
QFile wave_file(wave_fn);
if (wave_file.open(QFile::ReadWrite)) {
// We use S32 as a size-compatible substitute for SampleSummer::Sum which is 4 bytes in size
AudioRenderingParams waveform_params(SampleSummer::kSumSampleRate, audio_params_.channel_layout(), SampleFormat::SAMPLE_FMT_S32);
int chunk_size = (audio_params().sample_rate() / waveform_params.sample_rate());
{
// Write metadata header
SampleSummer::Info info;
info.channels = audio_params_.channel_count();
wave_file.write(reinterpret_cast<char*>(&info), sizeof(SampleSummer::Info));
}
qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in());
qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length());
qint64 end_offset = start_offset + length_offset;
if (wave_file.size() < end_offset) {
wave_file.resize(end_offset);
}
wave_file.seek(start_offset);
for (int i=0;i<samples_from_this_block->sample_count_per_channel();i+=chunk_size) {
QVector<SampleSummer::Sum> summary = SampleSummer::SumSamples(samples_from_this_block,
i,
qMin(chunk_size, samples_from_this_block->sample_count_per_channel() - i));
wave_file.write(reinterpret_cast<const char*>(summary.constData()),
summary.size() * sizeof(SampleSummer::Sum));
}
wave_file.close();
if (src_block->type() == Block::kClip) {
emit static_cast<ClipBlock*>(src_block)->PreviewUpdated();
}
}
}
NodeValueTable::Merge({merged_table, table});
}
merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer));
return merged_table;
}
void AudioRenderWorker::FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable *table)
{
if (stream->type() != Stream::kAudio) {
return;
}
NodeValue value = GetDataFromStream(stream, input_time);
table->Push(value);
}
const AudioRenderingParams &AudioRenderWorker::audio_params() const
{
return audio_params_;
}
OLIVE_NAMESPACE_EXIT
-59
View File
@@ -1,59 +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 AUDIORENDERWORKER_H
#define AUDIORENDERWORKER_H
#include "renderworker.h"
OLIVE_NAMESPACE_ENTER
class AudioRenderWorker : public RenderWorker
{
Q_OBJECT
public:
AudioRenderWorker(QHash<Node*, Node*>* copy_map, QObject* parent = nullptr);
void SetParameters(const AudioRenderingParams& audio_params);
signals:
void ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params);
protected:
virtual bool InitInternal() override;
virtual void CloseInternal() override;
virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range) override;
virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) override;
const AudioRenderingParams& audio_params() const;
private:
AudioRenderingParams audio_params_;
QHash<Node*, Node*>* copy_map_;
};
OLIVE_NAMESPACE_EXIT
#endif // AUDIORENDERWORKER_H
+26 -43
View File
@@ -22,7 +22,6 @@
#include <QtConcurrent/QtConcurrent>
#include "render/backend/audio/audiobackend.h"
#include "render/backend/opengl/openglbackend.h"
#include "render/colormanager.h"
#include "render/pixelformat.h"
@@ -36,8 +35,7 @@ Exporter::Exporter(ViewerOutput *viewer_node,
QObject(parent),
viewer_node_(viewer_node),
params_(params),
video_backend_(nullptr),
audio_backend_(nullptr),
renderer_(nullptr),
export_status_(false),
export_msg_(tr("Export hasn't started yet"))
{
@@ -54,7 +52,7 @@ Exporter::Exporter(ViewerOutput *viewer_node,
if (params_.has_custom_range()) {
export_range_ = params_.custom_range();
} else {
export_range_ = TimeRange(0, viewer_node_->Length());
export_range_ = TimeRange(0, viewer_node_->GetLength());
}
if (params_.video_enabled()) {
@@ -87,18 +85,10 @@ const QString &Exporter::GetExportError() const
void Exporter::Cancel()
{
if (video_backend_) {
video_backend_->CancelQueue();
video_backend_->Close();
video_backend_->deleteLater();
video_backend_ = nullptr;
}
if (audio_backend_) {
audio_backend_->CancelQueue();
audio_backend_->Close();
audio_backend_->deleteLater();
audio_backend_ = nullptr;
if (renderer_) {
renderer_->CancelQueue();
renderer_->deleteLater();
renderer_ = nullptr;
}
SetExportMessage(tr("User cancelled export"));
@@ -111,25 +101,18 @@ void Exporter::StartExporting()
export_status_ = false;
// Create renderers
if (!video_done_) {
video_backend_ = new OpenGLBackend();
renderer_ = new OpenGLBackend();
renderer_->SetViewerNode(viewer_node_);
video_backend_->SetLimitCaching(false);
video_backend_->SetViewerNode(viewer_node_);
video_backend_->SetParameters(VideoRenderingParams(viewer_node_->video_params().width(),
viewer_node_->video_params().height(),
params_.video_params().time_base(),
params_.video_params().format(),
params_.video_params().mode()));
if (!video_done_) {
renderer_->SetPixelFormat(params_.video_params().format());
renderer_->SetMode(params_.video_params().mode());
waiting_for_frame_ = 0;
}
if (!audio_done_) {
audio_backend_ = new AudioBackend();
audio_backend_->SetViewerNode(viewer_node_);
audio_backend_->SetParameters(params_.audio_params());
renderer_->SetSampleFormat(params_.audio_params().format());
}
// Open encoder and wait for result
@@ -153,10 +136,9 @@ void Exporter::ExportSucceeded()
return;
}
if (video_backend_) {
video_backend_->Close();
video_backend_->deleteLater();
video_backend_ = nullptr;
if (renderer_) {
renderer_->deleteLater();
renderer_ = nullptr;
}
export_status_ = true;
@@ -189,10 +171,10 @@ void Exporter::EncodeFrame()
waiting_for_frame_ += params_.video_params().time_base();
// Calculate progress
emit ProgressChanged(waiting_for_frame_.toDouble() / viewer_node_->Length().toDouble());
emit ProgressChanged(waiting_for_frame_.toDouble() / viewer_node_->GetLength().toDouble());
}
if (waiting_for_frame_ >= viewer_node_->Length()) {
if (waiting_for_frame_ >= viewer_node_->GetLength()) {
video_done_ = true;
debug_timer_.stop();
@@ -261,6 +243,7 @@ void Exporter::FrameRendered(FramePtr frame)
void Exporter::AudioRendered()
{
/*
// Retrieve the audio filename
QString cache_fn = audio_backend_->CachePathName();
@@ -270,11 +253,7 @@ void Exporter::AudioRendered()
OLIVE_NS_ARG(AudioRenderingParams, audio_backend_->params()),
Q_ARG(const QString&, cache_fn),
OLIVE_NS_ARG(TimeRange, export_range_));
// We don't need the audio backend anymorea
audio_backend_->Close();
audio_backend_->deleteLater();
audio_backend_ = nullptr;
*/
}
void Exporter::AudioEncodeComplete()
@@ -286,6 +265,7 @@ void Exporter::AudioEncodeComplete()
void Exporter::EncoderOpenedSuccessfully()
{
/*
// Invalidate caches
if (!video_done_) {
// First we generate the hashes so we know exactly how many frames we need
@@ -301,6 +281,7 @@ void Exporter::EncoderOpenedSuccessfully()
audio_backend_->InvalidateCache(export_range_, nullptr);
}
*/
}
void Exporter::EncoderOpenFailed()
@@ -317,12 +298,13 @@ void Exporter::EncoderClosed()
void Exporter::VideoHashesComplete()
{
/*
// We've got our hashes, time to kick off actual rendering
disconnect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
// Determine what frames will be hashed
TimeRangeList ranges;
ranges.append(TimeRange(0, viewer_node_->Length()));
ranges.append(TimeRange(0, viewer_node_->GetLength()));
// Set video backend to render mode but NOT hash or download
video_backend_->SetOperatingMode(VideoRenderWorker::kRenderOnly);
@@ -347,6 +329,7 @@ void Exporter::VideoHashesComplete()
foreach (const TimeRange& range, ranges) {
video_backend_->InvalidateCache(range, nullptr);
}
*/
}
void Exporter::DebugTimerMessage()
@@ -356,7 +339,7 @@ void Exporter::DebugTimerMessage()
void Exporter::FrameColorFinished()
{
if (!video_backend_ && !audio_backend_) {
if (!renderer_) {
return;
}
@@ -366,7 +349,7 @@ void Exporter::FrameColorFinished()
debug_timer_.stop();
const QMap<rational, QByteArray>& time_hash_map = video_backend_->frame_cache()->time_hash_map();
const QMap<rational, QByteArray>& time_hash_map = viewer_node_->video_frame_cache()->time_hash_map();
QByteArray this_hash = time_hash_map.value(frame->timestamp());
+2 -4
View File
@@ -28,9 +28,8 @@
#include "codec/encoder.h"
#include "node/output/viewer/viewer.h"
#include "render/backend/audiorenderbackend.h"
#include "render/backend/exportparams.h"
#include "render/backend/videorenderbackend.h"
#include "render/backend/renderbackend.h"
#include "render/colorprocessor.h"
OLIVE_NAMESPACE_ENTER
@@ -76,8 +75,7 @@ private:
ExportParams params_;
// Renderers
VideoRenderBackend* video_backend_;
AudioRenderBackend* audio_backend_;
RenderBackend* renderer_;
// Export transform
QMatrix4x4 transform_;
-2
View File
@@ -33,7 +33,5 @@ set(OLIVE_SOURCES
render/backend/opengl/opengltexture.cpp
render/backend/opengl/opengltexturecache.h
render/backend/opengl/opengltexturecache.cpp
render/backend/opengl/openglworker.h
render/backend/opengl/openglworker.cpp
PARENT_SCOPE
)
+2 -67
View File
@@ -20,77 +20,12 @@
#include "openglbackend.h"
#include <QEventLoop>
#include <QThread>
#include "openglrenderfunctions.h"
OLIVE_NAMESPACE_ENTER
OpenGLBackend::OpenGLBackend(QObject *parent) :
VideoRenderBackend(parent),
proxy_(nullptr)
OpenGLBackend::OpenGLBackend(QObject* parent) :
RenderBackend(parent)
{
}
OpenGLBackend::~OpenGLBackend()
{
Close();
}
bool OpenGLBackend::InitInternal()
{
if (!VideoRenderBackend::InitInternal()) {
return false;
}
proxy_ = new OpenGLProxy();
proxy_->SetParameters(params());
QThread* proxy_thread = new QThread();
proxy_thread->start(QThread::IdlePriority);
proxy_->moveToThread(proxy_thread);
if (!proxy_->Init()) {
proxy_thread->quit();
proxy_thread->wait();
proxy_thread->deleteLater();
proxy_->deleteLater();
proxy_ = nullptr;
return false;
}
// Initiate one thread per CPU core
for (int i=0;i<threads().size();i++) {
// Create one processor object for each thread
OpenGLWorker* processor = new OpenGLWorker(frame_cache(), proxy_);
processor->SetParameters(params());
processors_.append(processor);
}
return true;
}
void OpenGLBackend::CloseInternal()
{
if (proxy_) {
proxy_->thread()->quit();
proxy_->thread()->wait();
proxy_->thread()->deleteLater();
proxy_->deleteLater();
proxy_ = nullptr;
}
VideoRenderBackend::CloseInternal();
}
void OpenGLBackend::ParamsChangedEvent()
{
// If we're initiated, we need to recreate the texture. Otherwise this backend isn't active so it doesn't matter.
if (IsInitiated()) {
proxy_->SetParameters(params());
}
}
OLIVE_NAMESPACE_EXIT
+5 -16
View File
@@ -21,32 +21,21 @@
#ifndef OPENGLBACKEND_H
#define OPENGLBACKEND_H
#include "../videorenderbackend.h"
#include "openglframebuffer.h"
#include "openglproxy.h"
#include "openglshader.h"
#include "opengltexture.h"
#include "openglworker.h"
#include "render/backend/renderbackend.h"
OLIVE_NAMESPACE_ENTER
class OpenGLBackend : public VideoRenderBackend
class OpenGLBackend : public RenderBackend
{
Q_OBJECT
public:
OpenGLBackend(QObject* parent = nullptr);
virtual ~OpenGLBackend() override;
protected:
virtual bool InitInternal() override;
virtual void TextureToFrame(const QVariant& texture, FramePtr frame) const override;
virtual void CloseInternal() override;
virtual QVariant FrameToTexture(FramePtr frame) const override;
virtual void ParamsChangedEvent() override;
private:
OpenGLProxy* proxy_;
virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) const override;
};
@@ -22,6 +22,7 @@
#define OPENGLCOLORPROCESSOR_H
#include "openglshader.h"
#include "render/backend/rendercache.h"
#include "render/colorprocessor.h"
OLIVE_NAMESPACE_ENTER
@@ -62,6 +63,8 @@ private slots:
};
using OpenGLColorProcessorCache = RenderCache<QString, OpenGLColorProcessorPtr>;
OLIVE_NAMESPACE_EXIT
#endif // OPENGLCOLORPROCESSOR_H
+4 -2
View File
@@ -24,7 +24,9 @@
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include "../videorenderworker.h"
#include "common/timerange.h"
#include "node/value.h"
#include "openglcolorprocessor.h"
#include "openglframebuffer.h"
#include "openglshadercache.h"
#include "opengltexturecache.h"
@@ -81,7 +83,7 @@ private:
OpenGLFramebuffer buffer_;
ColorProcessorCache color_cache_;
OpenGLColorProcessorCache color_cache_;
VideoRenderingParams video_params_;
@@ -1,84 +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 "openglworker.h"
#include "common/clamp.h"
#include "core.h"
#include "node/block/transition/transition.h"
#include "node/node.h"
#include "openglcolorprocessor.h"
#include "openglrenderfunctions.h"
#include "render/colormanager.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
OpenGLWorker::OpenGLWorker(VideoRenderFrameCache *frame_cache, OpenGLProxy *proxy, QObject *parent) :
VideoRenderWorker(frame_cache, parent),
proxy_(proxy)
{
}
NodeValue OpenGLWorker::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range)
{
FramePtr frame = decoder->RetrieveVideo(range.in(),
video_params().divider(),
video_params().mode() == RenderMode::kOffline);
NodeValue value;
if (frame) {
QMetaObject::invokeMethod(proxy_,
"FrameToValue",
Qt::BlockingQueuedConnection,
OLIVE_NS_RETURN_ARG(NodeValue, value),
OLIVE_NS_ARG(FramePtr, frame),
OLIVE_NS_ARG(StreamPtr, stream));
}
return value;
}
void OpenGLWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params)
{
QMetaObject::invokeMethod(proxy_,
"RunNodeAccelerated",
Qt::BlockingQueuedConnection,
OLIVE_NS_CONST_ARG(Node*, node),
OLIVE_NS_CONST_ARG(TimeRange&, range),
OLIVE_NS_ARG(NodeValueDatabase&, input_params),
OLIVE_NS_ARG(NodeValueTable&, output_params));
}
void OpenGLWorker::TextureToBuffer(const QVariant &tex_in, int width, int height, const QMatrix4x4& matrix, void *buffer, int linesize)
{
QMetaObject::invokeMethod(proxy_,
"TextureToBuffer",
Qt::BlockingQueuedConnection,
Q_ARG(const QVariant&, tex_in),
Q_ARG(int, width),
Q_ARG(int, height),
Q_ARG(const QMatrix4x4&, matrix),
Q_ARG(void*, buffer),
Q_ARG(int, linesize));
}
OLIVE_NAMESPACE_EXIT
-56
View File
@@ -1,56 +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 OPENGLPROCESSOR_H
#define OPENGLPROCESSOR_H
#include <QOffscreenSurface>
#include <QOpenGLContext>
#include "../videorenderworker.h"
#include "openglframebuffer.h"
#include "openglproxy.h"
#include "openglshadercache.h"
#include "opengltexturecache.h"
OLIVE_NAMESPACE_ENTER
class OpenGLWorker : public VideoRenderWorker {
Q_OBJECT
public:
OpenGLWorker(VideoRenderFrameCache* frame_cache,
OpenGLProxy* proxy,
QObject* parent = nullptr);
protected:
virtual NodeValue FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) override;
virtual void RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable& output_params) override;
virtual void TextureToBuffer(const QVariant& texture, int width, int height, const QMatrix4x4& matrix, void *buffer, int linesize) override;
private:
OpenGLProxy* proxy_;
};
OLIVE_NAMESPACE_EXIT
#endif // OPENGLPROCESSOR_H
+342 -331
View File
@@ -23,6 +23,7 @@
#include <QDateTime>
#include <QThread>
#include "config/config.h"
#include "core.h"
#include "window/mainwindow/mainwindow.h"
@@ -30,331 +31,409 @@ OLIVE_NAMESPACE_ENTER
RenderBackend::RenderBackend(QObject *parent) :
QObject(parent),
started_(false),
viewer_node_(nullptr),
copied_viewer_node_(nullptr)
divider_(1),
render_mode_(RenderMode::kOnline),
pix_fmt_(PixelFormat::PIX_FMT_RGBA32F),
sample_fmt_(SampleFormat::SAMPLE_FMT_FLT)
{
// FIXME: Don't create in CLI mode
cancel_dialog_ = new RenderCancelDialog(Core::instance()->main_window());
}
bool RenderBackend::Init()
void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
{
if (started_) {
return true;
}
threads_.resize(QThread::idealThreadCount());
for (int i=0;i<threads_.size();i++) {
QThread* thread = new QThread(this);
threads_.replace(i, thread);
// We use low priority to keep the app responsive at all times (GUI thread should always prioritize over this one)
thread->start(QThread::IdlePriority);
}
cancel_dialog_->SetWorkerCount(threads_.size());
started_ = InitInternal();
// Connects workers and moves them to their respective threads
InitWorkers();
if (!started_) {
Close();
}
return started_;
}
void RenderBackend::Close()
{
if (!started_) {
if (viewer_node_ == viewer_node) {
return;
}
started_ = false;
CancelQueue();
SetViewerNode(nullptr);
CloseInternal();
for (int i=0;i<processors_.size();i++) {
// Invoke close and quit signals on processor and thread
QMetaObject::invokeMethod(processors_.at(i),
"Close",
Qt::QueuedConnection);
threads_.at(i)->quit();
}
for (int i=0;i<processors_.size();i++) {
threads_.at(i)->wait(); // FIXME: Maximum time in case a thread is stuck?
delete threads_.at(i);
delete processors_.at(i);
}
threads_.clear();
processors_.clear();
}
const QString &RenderBackend::GetError() const
{
return error_;
}
void RenderBackend::SetViewerNode(ViewerOutput *viewer_node)
{
if (viewer_node_) {
// Clear queue and wait for any currently running actions to complete
CancelQueue();
DisconnectViewer(viewer_node_);
// Delete all of our copied nodes
video_copy_map_.Clear();
audio_copy_map_.Clear();
copied_graph_.Clear();
copied_viewer_node_ = nullptr;
node_copy_map_.clear();
disconnect(viewer_node_,
&ViewerOutput::GraphChangedFrom,
this,
&RenderBackend::NodeGraphChanged);
disconnect(viewer_node_->audio_playback_cache(),
&AudioPlaybackCache::Invalidated,
this,
&RenderBackend::AudioCallback);
}
// Set viewer node
viewer_node_ = viewer_node;
if (viewer_node_) {
ConnectViewer(viewer_node_);
// Start copying viewer
video_copy_map_.Init(viewer_node_);
audio_copy_map_.Init(viewer_node_);
RegenerateCacheID();
video_copy_map_.Queue(viewer_node_->texture_input());
audio_copy_map_.Queue(viewer_node_->samples_input());
copied_viewer_node_ = static_cast<ViewerOutput*>(viewer_node_->copy());
copied_graph_.AddNode(copied_viewer_node_);
node_copy_map_.insert(viewer_node_, copied_viewer_node_);
video_copy_map_.ProcessQueue();
audio_copy_map_.ProcessQueue();
InvalidateCache(TimeRange(0, RATIONAL_MAX),
static_cast<NodeInput*>(viewer_node_->GetInputWithID(GetDependentInput()->id())));
connect(viewer_node_,
&ViewerOutput::GraphChangedFrom,
this,
&RenderBackend::NodeGraphChanged);
connect(viewer_node_->audio_playback_cache(),
&AudioPlaybackCache::Invalidated,
this,
&RenderBackend::AudioCallback);
}
}
bool RenderBackend::IsInitiated()
{
return started_;
}
void RenderBackend::RegenerateCacheID()
{
QCryptographicHash hash(QCryptographicHash::Sha1);
if (!viewer_node_
|| !GenerateCacheIDInternal(hash)) {
cache_id_.clear();
CacheIDChangedEvent(QString());
return;
}
hash.addData(viewer_node_->uuid().toByteArray());
QByteArray bytes = hash.result();
cache_id_ = bytes.toHex();
CacheIDChangedEvent(cache_id_);
}
bool RenderBackend::InitInternal()
{
return true;
}
void RenderBackend::CloseInternal()
{
}
bool RenderBackend::CanRender()
{
return true;
}
TimeRange RenderBackend::PopNextFrameFromQueue()
{
return cache_queue_.takeFirst();
}
rational RenderBackend::GetSequenceLength()
{
if (viewer_node_ == nullptr) {
return 0;
}
return viewer_node_->Length();
}
void RenderBackend::SetError(const QString &error)
{
error_ = error;
}
void RenderBackend::ConnectViewer(ViewerOutput *node)
{
Q_UNUSED(node)
}
void RenderBackend::DisconnectViewer(ViewerOutput *node)
{
Q_UNUSED(node)
}
void RenderBackend::CacheNext()
{
if (cache_queue_.isEmpty()) {
if (AllProcessorsAreAvailable()) {
emit QueueComplete();
}
return;
}
if (!ViewerIsConnected()
|| !CanRender()
|| !Init()) {
return;
}
while (!input_update_queued_.isEmpty()) {
if (!AllProcessorsAreAvailable()) {
// To update the inputs, we need all workers to stop
return;
}
CopyNodeInputValue(input_update_queued_.takeFirst());
}
Node* node_connected_to_viewer = GetDependentInput()->get_connected_node();
if (!node_connected_to_viewer) {
return;
}
foreach (RenderWorker* worker, processors_) {
if (cache_queue_.isEmpty()) {
break;
}
if (!WorkerIsBusy(worker)) {
TimeRange cache_frame = PopNextFrameFromQueue();
NodeDependency dep = NodeDependency(node_connected_to_viewer,
cache_frame);
// Timestamp this render job
qint64 job_time = QDateTime::currentMSecsSinceEpoch();
// Ensure the job's time is unique (since that's the whole point)
// NOTE: This value will be 0 if it doesn't exist, which will never be the result of currentMSecsSinceEpoch so we
// can safely assume 0 means it doesn't exist.
qint64 existing_job_time = render_job_info_.value(cache_frame);
if (existing_job_time == job_time) {
job_time = existing_job_time + 1;
}
render_job_info_.insert(cache_frame, job_time);
SetWorkerBusyState(worker, true);
cancel_dialog_->WorkerStarted();
WorkerAboutToStartEvent(worker);
QMetaObject::invokeMethod(worker,
"Render",
Qt::QueuedConnection,
OLIVE_NS_ARG(NodeDependency, dep),
Q_ARG(qint64, job_time));
}
}
}
ViewerOutput *RenderBackend::viewer_node() const
{
return copied_viewer_node_;
}
void RenderBackend::CancelQueue()
{
cache_queue_.clear();
// FIXME: Implement something better than this...
video_copy_map_.thread_pool()->waitForDone();
audio_copy_map_.thread_pool()->waitForDone();
}
int busy = 0;
for (int i=0;i<processor_busy_state_.size();i++) {
if (processor_busy_state_.at(i))
busy++;
QByteArray HashInternal(Node *node,
const VideoRenderingParams &params,
const rational &time)
{
QCryptographicHash hasher(QCryptographicHash::Sha1);
// Embed video parameters into this hash
hasher.addData(reinterpret_cast<const char*>(&params.effective_width()), sizeof(int));
hasher.addData(reinterpret_cast<const char*>(&params.effective_height()), sizeof(int));
hasher.addData(reinterpret_cast<const char*>(&params.format()), sizeof(PixelFormat::Format));
hasher.addData(reinterpret_cast<const char*>(&params.mode()), sizeof(RenderMode::Mode));
node->Hash(hasher, time);
return hasher.result();
}
QFuture<QByteArray> RenderBackend::Hash(const rational &time)
{
if (!viewer_node_) {
return QFuture<QByteArray>();
}
if (busy) {
qDebug() << this << "is waiting for" << busy << "busy workers";
return QtConcurrent::run(video_copy_map_.thread_pool(),
HashInternal,
viewer_node_,
video_params(),
time);
}
QFuture<FramePtr> RenderBackend::RenderFrame(const rational &time)
{
if (!viewer_node_) {
return QFuture<FramePtr>();
}
cancel_dialog_->RunIfWorkersAreBusy();
return QtConcurrent::run(video_copy_map_.thread_pool(),
this,
&RenderBackend::RenderFrameInternal,
time);
}
void RenderBackend::InvalidateCache(const TimeRange &range, NodeInput *from)
void RenderBackend::SetDivider(const int &divider)
{
// Adjust range to min/max values
rational start_range_adj = qMax(rational(0), range.in());
rational end_range_adj = qMin(GetSequenceLength(), range.out());
divider_ = divider;
}
qDebug() << "Cache invalidated between"
<< start_range_adj.toDouble()
<< "and"
<< end_range_adj.toDouble();
void RenderBackend::SetMode(const RenderMode::Mode &mode)
{
render_mode_ = mode;
}
if (from) {
// Queue value update
qDebug() << " from" << from->parentNode()->id() << "::" << from->id();
QueueValueUpdate(from);
void RenderBackend::SetPixelFormat(const PixelFormat::Format &pix_fmt)
{
pix_fmt_ = pix_fmt;
}
void RenderBackend::SetSampleFormat(const SampleFormat::Format &sample_fmt)
{
sample_fmt_ = sample_fmt;
}
void RenderBackend::NodeGraphChanged(NodeInput *from, NodeInput *source)
{
if (from == viewer_node_->texture_input()) {
video_copy_map_.Queue(source);
} else if (from == viewer_node_->samples_input()) {
audio_copy_map_.Queue(source);
}
}
void RenderBackend::FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable *table) const
{
if (stream->type() == Stream::kVideo || stream->type() == Stream::kImage) {
ImageStreamPtr video_stream = std::static_pointer_cast<ImageStream>(stream);
rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time.in();
QString colorspace_match = video_stream->get_colorspace_match_string();
NodeValue value;
bool found_cache = false;
if (still_image_cache_.Has(stream.get())) {
CachedStill cs = still_image_cache_.Get(stream.get());
if (cs.colorspace == colorspace_match
&& cs.alpha_is_associated == video_stream->premultiplied_alpha()
&& cs.divider == video_params_.divider()
&& cs.time == time_match) {
value = cs.texture;
found_cache = true;
} else {
still_image_cache_.Remove(stream.get());
}
}
if (!found_cache) {
value = GetDataFromStream(stream, input_time);
still_image_cache_.Add(stream.get(), {value,
colorspace_match,
video_stream->premultiplied_alpha(),
video_params_.divider(),
time_match});
}
table->Push(value);
} else if (stream->type() != Stream::kAudio) {
NodeValue value = GetDataFromStream(stream, input_time);
table->Push(value);
}
}
NodeValueTable RenderBackend::GenerateBlockTable(const TrackOutput *track, const TimeRange &range) const
{
if (track->track_type() == Timeline::kTrackTypeAudio) {
QList<Block*> active_blocks = track->BlocksAtTimeRange(range);
// All these blocks will need to output to a buffer so we create one here
SampleBufferPtr block_range_buffer = SampleBuffer::CreateAllocated(audio_params_,
audio_params_.time_to_samples(range.length()));
block_range_buffer->fill(0);
NodeValueTable merged_table;
// Loop through active blocks retrieving their audio
foreach (Block* b, active_blocks) {
TimeRange range_for_block(qMax(b->in(), range.in()),
qMin(b->out(), range.out()));
int destination_offset = audio_params_.time_to_samples(range_for_block.in() - range.in());
int max_dest_sz = audio_params_.time_to_samples(range_for_block.length());
// Destination buffer
NodeValueTable table = ProcessNode(NodeDependency(b, range_for_block));
QVariant sample_val = table.Take(NodeParam::kSamples);
SampleBufferPtr samples_from_this_block;
if (sample_val.isNull()
|| !(samples_from_this_block = sample_val.value<SampleBufferPtr>())) {
// If we retrieved no samples from this block, do nothing
continue;
}
// Stretch samples here
rational abs_speed = qAbs(b->speed());
if (abs_speed != 1) {
samples_from_this_block->speed(abs_speed.toDouble());
}
if (b->is_reversed()) {
// Reverse the audio buffer
samples_from_this_block->reverse();
}
int copy_length = qMin(max_dest_sz, samples_from_this_block->sample_count_per_channel());
// Copy samples into destination buffer
block_range_buffer->set(samples_from_this_block->const_data(), destination_offset, copy_length);
{
// Save waveform to file
Block* src_block = static_cast<Block*>(copy_map_->key(b));
QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString());
QDir waveform_loc = local_appdata_dir.filePath(QStringLiteral("waveform"));
waveform_loc.mkpath(".");
QString wave_fn(waveform_loc.filePath(QString::number(reinterpret_cast<quintptr>(src_block))));
QFile wave_file(wave_fn);
if (wave_file.open(QFile::ReadWrite)) {
// We use S32 as a size-compatible substitute for SampleSummer::Sum which is 4 bytes in size
AudioRenderingParams waveform_params(SampleSummer::kSumSampleRate, audio_params_.channel_layout(), SampleFormat::SAMPLE_FMT_S32);
int chunk_size = (audio_params().sample_rate() / waveform_params.sample_rate());
{
// Write metadata header
SampleSummer::Info info;
info.channels = audio_params_.channel_count();
wave_file.write(reinterpret_cast<char*>(&info), sizeof(SampleSummer::Info));
}
qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in());
qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length());
qint64 end_offset = start_offset + length_offset;
if (wave_file.size() < end_offset) {
wave_file.resize(end_offset);
}
wave_file.seek(start_offset);
for (int i=0;i<samples_from_this_block->sample_count_per_channel();i+=chunk_size) {
QVector<SampleSummer::Sum> summary = SampleSummer::SumSamples(samples_from_this_block,
i,
qMin(chunk_size, samples_from_this_block->sample_count_per_channel() - i));
wave_file.write(reinterpret_cast<const char*>(summary.constData()),
summary.size() * sizeof(SampleSummer::Sum));
}
wave_file.close();
if (src_block->type() == Block::kClip) {
emit static_cast<ClipBlock*>(src_block)->PreviewUpdated();
}
}
}
NodeValueTable::Merge({merged_table, table});
}
merged_table.Push(NodeParam::kSamples, QVariant::fromValue(block_range_buffer));
return merged_table;
} else {
return NodeTraverser::GenerateBlockTable(track, range);
}
}
FramePtr RenderBackend::RenderFrameInternal(const rational &time) const
{
NodeValueTable table = GenerateTable(viewer_node_,
TimeRange(time, time + viewer_node_->video_params().time_base()));
QVariant texture = table.Get(NodeParam::kTexture);
FramePtr frame = Frame::Create();
frame->set_video_params(video_params());
frame->allocate();
if (texture.isNull()) {
memset(frame->data(), 0, frame->allocated_size());
} else {
TextureToFrame(texture, frame);
}
InvalidateCacheInternal(start_range_adj, end_range_adj);
return frame;
}
bool RenderBackend::ViewerIsConnected() const
VideoRenderingParams RenderBackend::video_params() const
{
return viewer_node_;
return VideoRenderingParams(viewer_node_->video_params(),
pix_fmt_,
render_mode_,
divider_);
}
const QString &RenderBackend::cache_id() const
void RenderBackend::AudioCallback()
{
return cache_id_;
qDebug() << "STUB";
/*
AudioPlaybackCache* pb_cache = viewer_node_->audio_playback_cache();
QThreadPool* thread_pool = audio_copy_map_.thread_pool();
while (!pb_cache->IsFullyValidated()
&& thread_pool->activeThreadCount() < thread_pool->maxThreadCount()) {
// FIXME: Trigger a background audio thread
TimeRange r = viewer_node_->audio_playback_cache()->GetInvalidatedRanges().first();
qDebug() << "FIXME: Start rendering audio at:" << r;
break;
}
*/
}
void RenderBackend::QueueValueUpdate(NodeInput* from)
bool RenderBackend::ConformWaitInfo::operator==(const RenderBackend::ConformWaitInfo &rhs) const
{
if (!input_update_queued_.isEmpty()) {
return rhs.stream == stream
&& rhs.stream_time == stream_time
&& rhs.affected_range == affected_range;
}
RenderBackend::CopyMap::CopyMap() :
original_viewer_(nullptr),
copied_viewer_(nullptr)
{
}
void RenderBackend::CopyMap::Init(ViewerOutput *viewer)
{
original_viewer_ = viewer;
copied_viewer_ = static_cast<ViewerOutput*>(original_viewer_->copy());
copy_map_.insert(original_viewer_, copied_viewer_);
}
void RenderBackend::CopyMap::Queue(NodeInput *input)
{
if (!queued_updates_.isEmpty()) {
// Remove any inputs that are dependents of this input since they may have been removed since
// it was queued
QList<Node*> deps = from->GetDependencies();
QList<Node*> deps = input->GetDependencies();
for (int i=0;i<input_update_queued_.size();i++) {
if (deps.contains(input_update_queued_.at(i)->parentNode())) {
for (int i=0;i<queued_updates_.size();i++) {
if (deps.contains(queued_updates_.at(i)->parentNode())) {
// We don't need to queue this value since this input supersedes it
input_update_queued_.removeAt(i);
queued_updates_.removeAt(i);
i--;
}
}
}
input_update_queued_.append(from);
queued_updates_.append(input);
}
bool RenderBackend::WorkerIsBusy(RenderWorker *worker) const
void RenderBackend::CopyMap::ProcessQueue()
{
return processor_busy_state_.at(processors_.indexOf(worker));
while (!queued_updates_.isEmpty()) {
CopyNodeInputValue(queued_updates_.takeFirst());
}
}
void RenderBackend::SetWorkerBusyState(RenderWorker *worker, bool busy)
void RenderBackend::CopyMap::Clear()
{
processor_busy_state_.replace(processors_.indexOf(worker), busy);
qDeleteAll(copy_map_);
copy_map_.clear();
original_viewer_ = nullptr;
copied_viewer_ = nullptr;
}
void RenderBackend::CopyNodeInputValue(NodeInput *input)
void RenderBackend::CopyMap::CopyNodeInputValue(NodeInput *input)
{
// Find our copy of this parameter
Node* our_copy_node = node_copy_map_.value(input->parentNode());
Node* our_copy_node = copy_map_.value(input->parentNode());
NodeInput* our_copy = our_copy_node->GetInputWithID(input->id());
// Copy the standard/keyframe values between these two inputs
@@ -372,8 +451,7 @@ void RenderBackend::CopyNodeInputValue(NodeInput *input)
QList<Node*> old_deps = our_copy->GetExclusiveDependencies();
foreach (Node* i, old_deps) {
Node* n = node_copy_map_.take(node_copy_map_.key(i));
copied_graph_.TakeNode(n);
Node* n = copy_map_.take(copy_map_.key(i));
delete n;
}
@@ -395,16 +473,15 @@ void RenderBackend::CopyNodeInputValue(NodeInput *input)
}
}
Node* RenderBackend::CopyNodeConnections(Node* src_node)
Node* RenderBackend::CopyMap::CopyNodeConnections(Node* src_node)
{
// Check if this node is already in the map
Node* dst_node = node_copy_map_.value(src_node);
Node* dst_node = copy_map_.value(src_node);
// If not, create it now
if (!dst_node) {
dst_node = src_node->copy();
copied_graph_.AddNode(dst_node);
node_copy_map_.insert(src_node, dst_node);
copy_map_.insert(src_node, dst_node);
}
// Make sure its values are copied
@@ -423,10 +500,8 @@ Node* RenderBackend::CopyNodeConnections(Node* src_node)
return dst_node;
}
void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_input)
void RenderBackend::CopyMap::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_input)
{
//qDebug() << "Copying input" << src_input->id() << "from" << src_input->parentNode()->id();
if (src_input->IsConnected()) {
Node* dst_node = CopyNodeConnections(src_input->get_connected_node());
@@ -437,68 +512,4 @@ void RenderBackend::CopyNodeMakeConnection(NodeInput* src_input, NodeInput* dst_
}
}
bool RenderBackend::AllProcessorsAreAvailable() const
{
foreach (bool busy, processor_busy_state_) {
if (busy) {
return false;
}
}
return true;
}
const QVector<QThread *> &RenderBackend::threads()
{
return threads_;
}
void RenderBackend::InvalidateCacheInternal(const rational &start_range, const rational &end_range)
{
// Add the range to the list
cache_queue_.InsertTimeRange(TimeRange(start_range, end_range));
CacheNext();
}
void RenderBackend::CacheIDChangedEvent(const QString &id)
{
Q_UNUSED(id)
}
void RenderBackend::WorkerAboutToStartEvent(RenderWorker *worker)
{
Q_UNUSED(worker)
}
void RenderBackend::InitWorkers()
{
for (int i=0;i<processors_.size();i++) {
RenderWorker* processor = processors_.at(i);
QThread* thread = threads().at(i);
// Connect to it
ConnectWorkerToThis(processor);
// Connect cancel dialog to it
connect(processor, &RenderWorker::CompletedCache, cancel_dialog_, &RenderCancelDialog::WorkerDone, Qt::QueuedConnection);
// 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();
}
processor_busy_state_.resize(processors_.size());
processor_busy_state_.fill(false);
}
bool RenderBackend::FootageWaitInfo::operator==(const RenderBackend::FootageWaitInfo &rhs) const
{
return rhs.stream == stream
&& rhs.stream_time == stream_time
&& rhs.affected_range == affected_range;
}
OLIVE_NAMESPACE_EXIT
+81 -108
View File
@@ -21,153 +21,126 @@
#ifndef RENDERBACKEND_H
#define RENDERBACKEND_H
#include <QLinkedList>
#include <QtConcurrent/QtConcurrent>
#include "dialog/rendercancel/rendercancel.h"
#include "decodercache.h"
#include "node/graph.h"
#include "node/output/viewer/viewer.h"
#include "renderworker.h"
#include "node/traverser.h"
#include "render/backend/colorprocessorcache.h"
OLIVE_NAMESPACE_ENTER
class RenderBackend : public QObject
class RenderBackend : public QObject, public NodeTraverser
{
Q_OBJECT
public:
RenderBackend(QObject* parent = nullptr);
bool Init();
void Close();
const QString& GetError() const;
void SetViewerNode(ViewerOutput* viewer_node);
bool IsInitiated();
ViewerOutput* viewer_node() const;
void CancelQueue();
public slots:
void InvalidateCache(const TimeRange &range, NodeInput *from);
QFuture<QByteArray> Hash(const rational& time);
signals:
void QueueComplete();
QFuture<FramePtr> RenderFrame(const rational& time);
void SetDivider(const int& divider);
void SetMode(const RenderMode::Mode& mode);
void SetPixelFormat(const PixelFormat::Format& pix_fmt);
void SetSampleFormat(const SampleFormat::Format& sample_fmt);
public slots:
void NodeGraphChanged(NodeInput *from, NodeInput *source);
protected:
void RegenerateCacheID();
virtual void TextureToFrame(const QVariant& texture, FramePtr frame) const = 0;
virtual bool InitInternal();
virtual QVariant FrameToTexture(FramePtr frame) const = 0;
virtual void CloseInternal();
virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) const override;
virtual bool CanRender();
virtual TimeRange PopNextFrameFromQueue();
rational GetSequenceLength();
const QVector<QThread*>& threads();
/**
* @brief Internal function for generating the cache ID
*/
virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) = 0;
virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range);
virtual void CacheIDChangedEvent(const QString& id);
virtual void WorkerAboutToStartEvent(RenderWorker* worker);
void SetError(const QString& error);
virtual void ConnectViewer(ViewerOutput* node);
virtual void DisconnectViewer(ViewerOutput* node);
/**
* @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();
void InitWorkers();
virtual NodeInput* GetDependentInput() = 0;
virtual void ConnectWorkerToThis(RenderWorker* worker) = 0;
bool ViewerIsConnected() const;
const QString& cache_id() const;
void QueueValueUpdate(NodeInput *from);
bool AllProcessorsAreAvailable() const;
bool WorkerIsBusy(RenderWorker* worker) const;
void SetWorkerBusyState(RenderWorker* worker, bool busy);
TimeRangeList cache_queue_;
QVector<RenderWorker*> processors_;
QHash<TimeRange, qint64> render_job_info_;
QHash<Node*, Node*> node_copy_map_;
NodeGraph copied_graph_;
virtual NodeValueTable GenerateBlockTable(const TrackOutput *track, const TimeRange &range) const override;
private:
void CopyNodeInputValue(NodeInput* input);
Node *CopyNodeConnections(Node *src_node);
void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input);
/**
* @brief Internal list of RenderProcessThreads
*/
QVector<QThread*> threads_;
/**
* @brief Internal variable that contains whether the Renderer has started or not
*/
bool started_;
FramePtr RenderFrameInternal(const rational& time) const;
/**
* @brief Internal reference to attached viewer node
*/
ViewerOutput* viewer_node_;
/**
* @brief Internal reference to the copied viewer node we made in the compilation process
*/
ViewerOutput* copied_viewer_node_;
/**
* @brief Error string that can be set in SetError() to handle failures
*/
QString error_;
QString cache_id_;
QList<NodeInput*> input_update_queued_;
QVector<bool> processor_busy_state_;
RenderCancelDialog* cancel_dialog_;
struct FootageWaitInfo {
VideoRenderingParams video_params() const;
class CopyMap {
public:
CopyMap();
void Init(ViewerOutput* viewer);
void Queue(NodeInput* input);
void ProcessQueue();
void Clear();
QThreadPool* thread_pool() {
return &thread_pool_;
}
private:
void CopyNodeInputValue(NodeInput* input);
Node *CopyNodeConnections(Node *src_node);
void CopyNodeMakeConnection(NodeInput *src_input, NodeInput *dst_input);
ViewerOutput* original_viewer_;
ViewerOutput* copied_viewer_;
QList<NodeInput*> queued_updates_;
QHash<Node*, Node*> copy_map_;
QThreadPool thread_pool_;
};
// VIDEO MEMBERS
CopyMap video_copy_map_;
int divider_;
RenderMode::Mode render_mode_;
PixelFormat::Format pix_fmt_;
ColorProcessorCache color_cache_;
struct CachedStill {
NodeValue texture;
QString colorspace;
bool alpha_is_associated;
int divider;
rational time;
};
RenderCache<Stream*, CachedStill> still_image_cache_;
// AUDIO MEMBERS
CopyMap audio_copy_map_;
SampleFormat::Format sample_fmt_;
struct ConformWaitInfo {
StreamPtr stream;
TimeRange affected_range;
rational stream_time;
bool operator==(const FootageWaitInfo& rhs) const;
bool operator==(const ConformWaitInfo& rhs) const;
};
QList<FootageWaitInfo> footage_wait_info_;
QList<ConformWaitInfo> footage_wait_info_;
private slots:
void AudioCallback();
};
-128
View File
@@ -1,128 +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 "renderworker.h"
#include <QThread>
#include "node/block/block.h"
OLIVE_NAMESPACE_ENTER
RenderWorker::RenderWorker(QObject *parent) :
QObject(parent),
started_(false)
{
}
bool RenderWorker::Init()
{
if (started_) {
return true;
}
if (!(started_ = InitInternal())) {
Close();
}
return started_;
}
void RenderWorker::Close()
{
CloseInternal();
decoder_cache_.Clear();
started_ = false;
}
void RenderWorker::Render(NodeDependency path, qint64 job_time)
{
path_ = path;
emit CompletedCache(path, RenderInternal(path, job_time), job_time);
}
NodeValueTable RenderWorker::RenderInternal(const NodeDependency &path, const qint64 &job_time)
{
Q_UNUSED(job_time)
return ProcessNode(path);
}
void RenderWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable& output_params)
{
Q_UNUSED(node)
Q_UNUSED(range)
Q_UNUSED(input_params)
Q_UNUSED(output_params)
}
DecoderPtr RenderWorker::ResolveDecoderFromInput(StreamPtr stream)
{
// Access a map of Node inputs and decoder instances and retrieve a frame!
DecoderPtr decoder = decoder_cache_.Get(stream.get());
if (!decoder && stream) {
// Create a new Decoder here
decoder = Decoder::CreateFromID(stream->footage()->decoder());
decoder->set_stream(stream);
if (decoder->Open()) {
decoder_cache_.Add(stream.get(), decoder);
} else {
decoder = nullptr;
qWarning() << "Failed to open decoder for" << stream->footage()->filename() << "::" << stream->index();
}
}
return decoder;
}
NodeValue RenderWorker::GetDataFromStream(StreamPtr stream, const TimeRange &input_time)
{
DecoderPtr decoder = ResolveDecoderFromInput(stream);
if (decoder) {
return FrameToValue(decoder, stream, input_time);
}
return NodeValue();
}
bool RenderWorker::IsStarted()
{
return started_;
}
void RenderWorker::ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params)
{
// Check if we have a shader for this output
RunNodeAccelerated(node, range, input_params, output_params);
}
const NodeDependency &RenderWorker::CurrentPath() const
{
return path_;
}
OLIVE_NAMESPACE_EXIT
-81
View File
@@ -1,81 +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 RENDERWORKER_H
#define RENDERWORKER_H
#include <QObject>
#include "decodercache.h"
#include "node/node.h"
#include "node/output/track/track.h"
#include "node/traverser.h"
OLIVE_NAMESPACE_ENTER
class RenderWorker : public QObject, public NodeTraverser
{
Q_OBJECT
public:
RenderWorker(QObject* parent = nullptr);
bool Init();
bool IsStarted();
public slots:
void Close();
void Render(OLIVE_NAMESPACE::NodeDependency path, qint64 job_time);
signals:
void CompletedCache(OLIVE_NAMESPACE::NodeDependency dep, OLIVE_NAMESPACE::NodeValueTable data, qint64 job_time);
protected:
virtual bool InitInternal() = 0;
virtual void CloseInternal() = 0;
virtual NodeValueTable RenderInternal(const NodeDependency& CurrentPath, const qint64& job_time);
virtual void RunNodeAccelerated(const Node *node, const TimeRange& range, NodeValueDatabase &input_params, NodeValueTable &output_params);
virtual NodeValue FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) = 0;
virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) override;
DecoderPtr ResolveDecoderFromInput(StreamPtr stream);
NodeValue GetDataFromStream(StreamPtr stream, const TimeRange& input_time);
const NodeDependency& CurrentPath() const;
private:
bool started_;
DecoderCache decoder_cache_;
NodeDependency path_;
};
OLIVE_NAMESPACE_EXIT
#endif // RENDERWORKER_H
-354
View File
@@ -1,354 +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 "videorenderbackend.h"
#include <OpenImageIO/imageio.h>
#include <QApplication>
#include <QCryptographicHash>
#include <QDebug>
#include <QDir>
#include <QtMath>
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "render/diskmanager.h"
#include "render/diskmanager.h"
#include "render/pixelformat.h"
#include "videorenderworker.h"
OLIVE_NAMESPACE_ENTER
VideoRenderBackend::VideoRenderBackend(QObject *parent) :
RenderBackend(parent),
operating_mode_(VideoRenderWorker::kRenderOnly),
only_signal_last_frame_requested_(true),
limit_caching_(true)
{
connect(DiskManager::instance(), &DiskManager::DeletedFrame, this, &VideoRenderBackend::FrameRemovedFromDiskCache);
}
void VideoRenderBackend::ConnectViewer(ViewerOutput *node)
{
connect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache);
connect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength);
}
void VideoRenderBackend::DisconnectViewer(ViewerOutput *node)
{
disconnect(node, &ViewerOutput::VideoChangedBetween, this, &VideoRenderBackend::InvalidateCache);
disconnect(node, &ViewerOutput::LengthChanged, this, &VideoRenderBackend::TruncateFrameCacheLength);
frame_cache_.Clear();
}
const VideoRenderingParams &VideoRenderBackend::params() const
{
return params_;
}
void VideoRenderBackend::SetParameters(const VideoRenderingParams& params)
{
CancelQueue();
// Set new parameters
params_ = params;
// Handle custom events from derivatives
ParamsChangedEvent();
// Set params on all processors
foreach (RenderWorker* worker, processors_) {
static_cast<VideoRenderWorker*>(worker)->SetParameters(params_);
}
// Regenerate the cache ID
RegenerateCacheID();
}
void VideoRenderBackend::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode)
{
CancelQueue();
operating_mode_ = mode;
foreach (RenderWorker* worker, processors_) {
static_cast<VideoRenderWorker*>(worker)->SetOperatingMode(operating_mode_);
}
}
void VideoRenderBackend::SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix)
{
foreach (RenderWorker* worker, processors_) {
static_cast<VideoRenderWorker*>(worker)->SetFrameGenerationParams(width, height, matrix);
}
}
void VideoRenderBackend::SetOnlySignalLastFrameRequested(bool enabled)
{
only_signal_last_frame_requested_ = enabled;
}
bool VideoRenderBackend::IsRendered(const rational &time) const
{
TimeRange range(time, time);
return !TimeIsQueued(range) && !render_job_info_.contains(range);
}
void VideoRenderBackend::SetLimitCaching(bool limit)
{
limit_caching_ = limit;
}
bool VideoRenderBackend::GenerateCacheIDInternal(QCryptographicHash& hash)
{
if (!params_.is_valid()) {
return false;
}
// Generate an ID that is more or less guaranteed to be unique to this Sequence
hash.addData(QString::number(params_.width()).toUtf8());
hash.addData(QString::number(params_.height()).toUtf8());
hash.addData(QString::number(params_.format()).toUtf8());
hash.addData(QString::number(params_.divider()).toUtf8());
return true;
}
void VideoRenderBackend::CacheIDChangedEvent(const QString &id)
{
frame_cache_.SetCacheID(id);
}
void VideoRenderBackend::ConnectWorkerToThis(RenderWorker *processor)
{
VideoRenderWorker* video_processor = static_cast<VideoRenderWorker*>(processor);
video_processor->SetOperatingMode(operating_mode_);
connect(video_processor, &VideoRenderWorker::HashAlreadyBeingCached, this, &VideoRenderBackend::ThreadSkippedFrame, Qt::QueuedConnection);
connect(video_processor, &VideoRenderWorker::CompletedDownload, this, &VideoRenderBackend::ThreadCompletedDownload, Qt::QueuedConnection);
connect(video_processor, &VideoRenderWorker::HashAlreadyExists, this, &VideoRenderBackend::ThreadHashAlreadyExists, Qt::QueuedConnection);
connect(video_processor, &VideoRenderWorker::GeneratedFrame, this, &VideoRenderBackend::GeneratedFrame, Qt::QueuedConnection);
connect(video_processor, &VideoRenderWorker::GeneratedFrame, this, &VideoRenderBackend::ThreadGeneratedFrame, Qt::QueuedConnection);
}
void VideoRenderBackend::InvalidateCacheInternal(const rational &start_range, const rational &end_range)
{
TimeRange invalidated(start_range, end_range);
if (limit_caching_) {
invalidated_.InsertTimeRange(invalidated);
} else {
RenderBackend::InvalidateCacheInternal(start_range, end_range);
}
emit RangeInvalidated(invalidated);
}
void VideoRenderBackend::RenderFrame(const rational &time)
{
RenderBackend::InvalidateCacheInternal(time, time);
}
VideoRenderFrameCache *VideoRenderBackend::frame_cache()
{
return &frame_cache_;
}
QString VideoRenderBackend::GetCachedFrame(const rational &time)
{
UpdateLastRequestedTime(time);
if (viewer_node() == nullptr) {
// Nothing is connected - nothing to show or render
return nullptr;
}
if (cache_id().isEmpty()) {
qWarning() << "No cache ID";
return nullptr;
}
if (!params_.is_valid()) {
qWarning() << "Invalid parameters";
return nullptr;
}
// Find frame in map
QByteArray frame_hash = frame_cache_.TimeToHash(time);
if (!frame_hash.isEmpty()) {
DiskManager::instance()->Accessed(frame_hash);
return frame_cache_.CachePathName(frame_hash, params_.format());
}
return QString();
}
void VideoRenderBackend::UpdateLastRequestedTime(const rational &time)
{
last_time_requested_ = time;
}
NodeInput *VideoRenderBackend::GetDependentInput()
{
return viewer_node()->texture_input();
}
bool VideoRenderBackend::CanRender()
{
return params_.is_valid();
}
TimeRange VideoRenderBackend::PopNextFrameFromQueue()
{
rational earliest_frame = RATIONAL_MAX;
// Find earliest frame in the cache queue
foreach (const TimeRange& range, cache_queue_) {
if (range.in() < earliest_frame) {
earliest_frame = range.in();
}
}
// Snap this frame to the timebase
rational snapped_frame = Timecode::snap_time_to_timebase(earliest_frame, params_.time_base());
rational next_frame;
if (snapped_frame > earliest_frame) {
next_frame = snapped_frame;
snapped_frame -= params_.time_base();
} else {
next_frame = snapped_frame + params_.time_base();
}
TimeRange frame_range(snapped_frame, next_frame);
// Remove this particular frame from the queue
cache_queue_.RemoveTimeRange(frame_range);
// Remove this particular frame from missing frames
invalidated_.RemoveTimeRange(frame_range);
// Return the snapped frame
return TimeRange(frame_range.in(), frame_range.in());
}
void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash)
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
SetFrameHash(dep, hash, job_time);
QList<rational> hashes_with_time = frame_cache()->FramesWithHash(hash);
foreach (const rational& t, hashes_with_time) {
emit CachedTimeReady(t, job_time);
}
// Queue up a new frame for this worker
CacheNext();
}
void VideoRenderBackend::ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash)
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
if (SetFrameHash(dep, hash, job_time)
&& frame_cache_.HasHash(hash, params_.format())) {
emit CachedTimeReady(dep.in(), job_time);
}
// Queue up a new frame for this worker
CacheNext();
}
void VideoRenderBackend::ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash)
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
if (SetFrameHash(dep, hash, job_time)) {
emit CachedTimeReady(dep.in(), job_time);
}
// Queue up a new frame for this worker
CacheNext();
}
void VideoRenderBackend::ThreadGeneratedFrame()
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
CacheNext();
}
void VideoRenderBackend::TruncateFrameCacheLength(const rational &length)
{
// Remove frames after this time code if it's changed
frame_cache_.Truncate(length);
invalidated_.RemoveTimeRange(TimeRange(length, RATIONAL_MAX));
// If the playhead is past the length, update the viewer to a null texture because it won't be cached through the
// queue, but will now be a null texture
if (last_time_requested_ >= length) {
emit CachedTimeReady(last_time_requested_, QDateTime::currentMSecsSinceEpoch());
}
}
void VideoRenderBackend::FrameRemovedFromDiskCache(const QByteArray &hash)
{
QList<rational> deleted_frames = frame_cache()->TakeFramesWithHash(hash);
foreach (const rational& frame, deleted_frames) {
TimeRange invalidated(frame, frame+params_.time_base());
invalidated_.InsertTimeRange(invalidated);
emit RangeInvalidated(invalidated);
}
}
bool VideoRenderBackend::TimeIsQueued(const TimeRange &time) const
{
return cache_queue_.ContainsTimeRange(time, true, false);
}
bool VideoRenderBackend::JobIsCurrent(const NodeDependency &dep, const qint64& job_time) const
{
return (render_job_info_.value(dep.range()) == job_time && !TimeIsQueued(dep.range()));
}
bool VideoRenderBackend::SetFrameHash(const NodeDependency &dep, const QByteArray &hash, const qint64& job_time)
{
if (JobIsCurrent(dep, job_time)) {
frame_cache_.SetHash(dep.in(), hash);
render_job_info_.remove(dep.range());
return true;
}
return false;
}
OLIVE_NAMESPACE_EXIT
-150
View File
@@ -1,150 +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 VIDEORENDERERBACKEND_H
#define VIDEORENDERERBACKEND_H
#include <QLinkedList>
#include "colorprocessorcache.h"
#include "node/output/viewer/viewer.h"
#include "renderbackend.h"
#include "render/pixelformat.h"
#include "render/rendermodes.h"
#include "videorenderframecache.h"
#include "videorenderworker.h"
OLIVE_NAMESPACE_ENTER
/**
* @brief A multithreaded OpenGL based renderer for node systems
*/
class VideoRenderBackend : public RenderBackend
{
Q_OBJECT
public:
/**
* @brief Renderer Constructor
*
* 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 = nullptr);
/**
* @brief Set parameters of the Renderer
*
* The Renderer owns the buffers that are used in the rendering process and this function sets the kind of buffers
* to use. The Renderer must be stopped when calling this function.
*/
void SetParameters(const VideoRenderingParams &params);
void SetOperatingMode(const VideoRenderWorker::OperatingMode& mode);
void SetFrameGenerationParams(int width, int height, const QMatrix4x4& matrix);
void SetOnlySignalLastFrameRequested(bool enabled);
bool IsRendered(const rational& time) const;
void SetLimitCaching(bool limit);
QString GetCachedFrame(const rational& time);
void UpdateLastRequestedTime(const rational& time);
VideoRenderFrameCache* frame_cache();
const VideoRenderingParams& params() const;
void RenderFrame(const rational& time);
protected:
struct HashTimeMapping {
rational time;
QByteArray hash;
};
virtual void ConnectViewer(ViewerOutput* node) override;
virtual void DisconnectViewer(ViewerOutput* node) override;
virtual NodeInput* GetDependentInput() override;
virtual bool CanRender() override;
virtual TimeRange PopNextFrameFromQueue() override;
/**
* @brief Internal function for generating the cache ID
*/
virtual bool GenerateCacheIDInternal(QCryptographicHash& hash) override;
virtual void CacheIDChangedEvent(const QString& id) override;
virtual void ConnectWorkerToThis(RenderWorker* processor) override;
virtual void InvalidateCacheInternal(const rational &start_range, const rational &end_range) override;
virtual void ParamsChangedEvent(){}
VideoRenderWorker::OperatingMode operating_mode_;
signals:
void CachedTimeReady(const rational& time, qint64 job_time);
void RangeInvalidated(const TimeRange& range);
void GeneratedFrame(FramePtr frame);
private:
bool TimeIsQueued(const TimeRange &time) const;
bool JobIsCurrent(const NodeDependency &dep, const qint64& job_time) const;
bool SetFrameHash(const NodeDependency& dep, const QByteArray& hash, const qint64& job_time);
VideoRenderingParams params_;
VideoRenderFrameCache frame_cache_;
TimeRangeList invalidated_;
rational last_time_requested_;
bool only_signal_last_frame_requested_;
bool limit_caching_;
private slots:
void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash);
void ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash);
void ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash);
void ThreadGeneratedFrame();
void TruncateFrameCacheLength(const rational& length);
void FrameRemovedFromDiskCache(const QByteArray& hash);
};
OLIVE_NAMESPACE_EXIT
#endif // VIDEORENDERERBACKEND_H
-251
View File
@@ -1,251 +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 "videorenderworker.h"
#include "common/define.h"
#include "common/functiontimer.h"
#include "node/block/transition/transition.h"
#include "node/node.h"
#include "project/project.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, QObject *parent) :
RenderWorker(parent),
frame_cache_(frame_cache),
operating_mode_(kHashRenderCache)
{
}
const VideoRenderingParams &VideoRenderWorker::video_params()
{
return video_params_;
}
void VideoRenderWorker::TextureToBuffer(const QVariant &texture, void *buffer, int linesize)
{
TextureToBuffer(texture,
video_params_.effective_width(),
video_params_.effective_height(),
QMatrix4x4(),
buffer,
linesize);
}
NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, const qint64 &job_time)
{
// Get hash of node graph
// We use SHA-1 for speed (benchmarks show it's the fastest hash available to us)
QByteArray hash;
if (operating_mode_ & kHashOnly) {
QCryptographicHash hasher(QCryptographicHash::Sha1);
// Embed video parameters into this hash
int vwidth = video_params_.effective_width();
int vheight = video_params_.effective_height();
PixelFormat::Format vfmt = video_params_.format();
RenderMode::Mode vmode = video_params_.mode();
hasher.addData(reinterpret_cast<const char*>(&vwidth), sizeof(int));
hasher.addData(reinterpret_cast<const char*>(&vheight), sizeof(int));
hasher.addData(reinterpret_cast<const char*>(&vfmt), sizeof(PixelFormat::Format));
hasher.addData(reinterpret_cast<const char*>(&vmode), sizeof(RenderMode::Mode));
path.node()->Hash(hasher, path.in());
hash = hasher.result();
}
NodeValueTable value;
if (!(operating_mode_ & kRenderOnly)) {
// Emit only the hash
emit CompletedDownload(path, job_time, hash);
} else if ((operating_mode_ & kHashOnly) && frame_cache_->HasHash(hash, video_params_.format())) {
// We've already cached this hash, no need to continue
emit HashAlreadyExists(path, job_time, hash);
} else if (!(operating_mode_ & kHashOnly) || frame_cache_->TryCache(hash)) {
// This hash is available for us to cache, start traversing graph
value = ProcessNode(path);
// Find texture in hash
QVariant texture = value.Get(NodeParam::kTexture);
// If we actually have a texture, download it into the disk cache
if (!texture.isNull() || (!(operating_mode_ & kDownloadOnly))) {
Download(hash, path.in(), texture);
}
frame_cache_->RemoveHashFromCurrentlyCaching(hash);
// Signal that this job is complete
if (operating_mode_ & kDownloadOnly) {
emit CompletedDownload(path, job_time, hash);
}
} else {
// Another thread must be caching this already, nothing to be done
emit HashAlreadyBeingCached(path, job_time, hash);
}
return value;
}
void VideoRenderWorker::FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable *table)
{
if (stream->type() != Stream::kVideo && stream->type() != Stream::kImage) {
return;
}
ImageStreamPtr video_stream = std::static_pointer_cast<ImageStream>(stream);
rational time_match = (stream->type() == Stream::kImage) ? rational() : input_time.in();
QString colorspace_match = video_stream->get_colorspace_match_string();
NodeValue value;
bool found_cache = false;
if (still_image_cache_.Has(stream.get())) {
CachedStill cs = still_image_cache_.Get(stream.get());
if (cs.colorspace == colorspace_match
&& cs.alpha_is_associated == video_stream->premultiplied_alpha()
&& cs.divider == video_params_.divider()
&& cs.time == time_match) {
value = cs.texture;
found_cache = true;
} else {
still_image_cache_.Remove(stream.get());
}
}
if (!found_cache) {
value = GetDataFromStream(stream, input_time);
still_image_cache_.Add(stream.get(), {value,
colorspace_match,
video_stream->premultiplied_alpha(),
video_params_.divider(),
time_match});
}
table->Push(value);
}
void VideoRenderWorker::SetParameters(const VideoRenderingParams &video_params)
{
video_params_ = video_params;
if (IsStarted()) {
ResizeDownloadBuffer();
}
ParametersChangedEvent();
}
void VideoRenderWorker::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode)
{
operating_mode_ = mode;
}
void VideoRenderWorker::SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix)
{
frame_gen_params_ = VideoRenderingParams(width,
height,
video_params_.time_base(),
video_params_.format(),
video_params_.mode(),
video_params_.divider());
frame_gen_mat_ = matrix;
}
bool VideoRenderWorker::InitInternal()
{
if (video_params_.is_valid()) {
ResizeDownloadBuffer();
}
return true;
}
void VideoRenderWorker::CloseInternal()
{
download_buffer_.clear();
}
void VideoRenderWorker::Download(const QByteArray& hash, const rational& time, QVariant texture)
{
if (operating_mode_ & kDownloadOnly) {
TextureToBuffer(texture, download_buffer_.data(), 0);
frame_cache_->SaveCacheFrame(hash,
download_buffer_.data(),
VideoRenderingParams(video_params_.effective_width(),
video_params_.effective_height(),
video_params_.format()));
} else {
FramePtr frame = Frame::Create();
if (frame_gen_params_.is_valid()) {
frame->set_video_params(frame_gen_params_);
} else {
frame->set_video_params(VideoRenderingParams(video_params_.effective_width(),
video_params_.effective_height(),
video_params_.format()));
}
frame->allocate();
if (texture.isNull()) {
memset(frame->data(), 0, frame->allocated_size());
} else {
TextureToBuffer(texture, frame->width(), frame->height(), frame_gen_mat_, frame->data(), frame->linesize_pixels());
}
frame->set_timestamp(time);
emit GeneratedFrame(frame);
}
}
void VideoRenderWorker::ResizeDownloadBuffer()
{
download_buffer_.resize(PixelFormat::GetBufferSize(video_params_.format(), video_params_.effective_width(), video_params_.effective_height()));
}
ColorProcessorCache *VideoRenderWorker::color_cache()
{
return &color_cache_;
}
OLIVE_NAMESPACE_EXIT
-140
View File
@@ -1,140 +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 VIDEORENDERWORKER_H
#define VIDEORENDERWORKER_H
#include <QCryptographicHash>
#include <QMatrix4x4>
#include "colorprocessorcache.h"
#include "node/dependency.h"
#include "render/videoparams.h"
#include "renderworker.h"
#include "videorenderframecache.h"
OLIVE_NAMESPACE_ENTER
class VideoRenderWorker : public RenderWorker {
Q_OBJECT
public:
/**
* @brief VideoRenderWorker uses hashes to recognize frames that are identical to others in the render queue
*
* This mode can modify the behavior of the worker, either to disable hash verification or only generate hashes and
* not render. These are only used in the context of exporting.
*
* These are also flags that can be or'd together, mostly for the convenience of checking which functionalities are
* disabled and enabled.
*/
enum OperatingMode {
/// Generate hashes but don't render or download anything
kHashOnly = 0x1,
/// Render but don't download or hash
kRenderOnly = 0x2,
/// Enable download (NEVER USE THIS ON ITS OWN, this is only here for checking flags, download-only mode makes no sense)
kDownloadOnly = 0x4,
/// Use hash verification and render, but don't cache any frames to disk
kHashAndRenderOnly = 0x3,
/// Render and download, but don't use hash verification
kRenderAndCacheOnly = 0x6,
/// Render and use hashes to identify exact matches (default)
kHashRenderCache = 0x7
};
VideoRenderWorker(VideoRenderFrameCache* frame_cache, QObject* parent = nullptr);
void SetParameters(const VideoRenderingParams& video_params);
void SetOperatingMode(const OperatingMode& mode);
void SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix);
signals:
void CompletedDownload(NodeDependency path, qint64 job_time, QByteArray hash);
void HashAlreadyBeingCached(NodeDependency path, qint64 job_time, QByteArray hash);
void HashAlreadyExists(NodeDependency path, qint64 job_time, QByteArray hash);
void GeneratedFrame(FramePtr frame);
void Aborted();
protected:
virtual bool InitInternal() override;
virtual void CloseInternal() override;
const VideoRenderingParams& video_params();
virtual void ParametersChangedEvent(){}
void TextureToBuffer(const QVariant& texture, void *buffer, int linesize);
virtual void TextureToBuffer(const QVariant& texture, int width, int height, const QMatrix4x4& matrix, void *buffer, int linesize) = 0;
virtual NodeValueTable RenderInternal(const NodeDependency& CurrentPath, const qint64& job_time) override;
virtual void FootageProcessingEvent(StreamPtr stream, const TimeRange &input_time, NodeValueTable* table) override;
ColorProcessorCache* color_cache();
private:
void Download(const QByteArray &hash, const rational &time, QVariant texture);
void ResizeDownloadBuffer();
VideoRenderingParams frame_gen_params_;
QMatrix4x4 frame_gen_mat_;
VideoRenderingParams video_params_;
VideoRenderFrameCache* frame_cache_;
ColorProcessorCache color_cache_;
QByteArray download_buffer_;
OperatingMode operating_mode_;
struct CachedStill {
NodeValue texture;
QString colorspace;
bool alpha_is_associated;
int divider;
rational time;
};
RenderCache<Stream*, CachedStill> still_image_cache_;
private slots:
};
OLIVE_NAMESPACE_EXIT
#endif // VIDEORENDERWORKER_H
@@ -18,7 +18,7 @@
***/
#include "videorenderframecache.h"
#include "framehashcache.h"
#include <OpenEXR/ImfFloatAttribute.h>
#include <OpenEXR/ImfInputFile.h>
@@ -33,80 +33,24 @@
OLIVE_NAMESPACE_ENTER
void VideoRenderFrameCache::Clear()
{
time_hash_map_.clear();
{
QMutexLocker locker(&currently_caching_lock_);
currently_caching_list_.clear();
}
cache_id_.clear();
}
bool VideoRenderFrameCache::HasHash(const QByteArray &hash, const PixelFormat::Format& format)
{
return QFileInfo::exists(CachePathName(hash, format)) && !IsCaching(hash);
}
bool VideoRenderFrameCache::IsCaching(const QByteArray &hash)
{
QMutexLocker locker(&currently_caching_lock_);
return currently_caching_list_.contains(hash);
}
bool VideoRenderFrameCache::TryCache(const QByteArray &hash)
{
QMutexLocker locker(&currently_caching_lock_);
if (!currently_caching_list_.contains(hash)) {
currently_caching_list_.append(hash);
return true;
}
return false;
}
void VideoRenderFrameCache::SetCacheID(const QString &id)
{
Clear();
cache_id_ = id;
}
QByteArray VideoRenderFrameCache::TimeToHash(const rational &time) const
QByteArray FrameHashCache::GetHash(const rational &time) const
{
return time_hash_map_.value(time);
}
void VideoRenderFrameCache::SetHash(const rational &time, const QByteArray &hash)
void FrameHashCache::SetHash(const rational &time, const QByteArray &hash)
{
time_hash_map_.insert(time, hash);
Validate(TimeRange(time, time + timebase_));
}
void VideoRenderFrameCache::Truncate(const rational &time)
void FrameHashCache::SetTimebase(const rational &tb)
{
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++;
}
}
timebase_ = tb;
}
void VideoRenderFrameCache::RemoveHashFromCurrentlyCaching(const QByteArray &hash)
{
QMutexLocker locker(&currently_caching_lock_);
currently_caching_list_.removeOne(hash);
}
QList<rational> VideoRenderFrameCache::FramesWithHash(const QByteArray &hash) const
QList<rational> FrameHashCache::GetFramesWithHash(const QByteArray &hash) const
{
QList<rational> times;
@@ -121,7 +65,7 @@ QList<rational> VideoRenderFrameCache::FramesWithHash(const QByteArray &hash) co
return times;
}
QList<rational> VideoRenderFrameCache::TakeFramesWithHash(const QByteArray &hash)
QList<rational> FrameHashCache::TakeFramesWithHash(const QByteArray &hash)
{
QList<rational> times;
@@ -140,12 +84,12 @@ QList<rational> VideoRenderFrameCache::TakeFramesWithHash(const QByteArray &hash
return times;
}
const QMap<rational, QByteArray> &VideoRenderFrameCache::time_hash_map() const
const QMap<rational, QByteArray> &FrameHashCache::time_hash_map() const
{
return time_hash_map_;
}
QString VideoRenderFrameCache::GetFormatExtension(const PixelFormat::Format &f)
QString FrameHashCache::GetFormatExtension(const PixelFormat::Format &f)
{
if (PixelFormat::FormatIsFloat(f)) {
// EXR is only fast with float buffers so we only use it for those
@@ -160,9 +104,9 @@ QString VideoRenderFrameCache::GetFormatExtension(const PixelFormat::Format &f)
}
}
void VideoRenderFrameCache::SaveCacheFrame(const QByteArray& hash,
char* data,
const VideoRenderingParams& vparam) const
void FrameHashCache::SaveCacheFrame(const QByteArray& hash,
char* data,
const VideoRenderingParams& vparam)
{
QString fn = CachePathName(hash, vparam.format());
@@ -172,7 +116,35 @@ void VideoRenderFrameCache::SaveCacheFrame(const QByteArray& hash,
}
}
QString VideoRenderFrameCache::CachePathName(const QByteArray& hash, const PixelFormat::Format& pix_fmt) const
void FrameHashCache::LengthChangedEvent(const rational &old, const rational &newlen)
{
if (newlen < old) {
QMap<rational, QByteArray>::iterator i = time_hash_map_.begin();
while (i != time_hash_map_.end()) {
if (i.key() >= newlen) {
i = time_hash_map_.erase(i);
} else {
i++;
}
}
}
}
void FrameHashCache::InvalidateEvent(const TimeRange &r)
{
QMap<rational, QByteArray>::iterator i = time_hash_map_.begin();
while (i != time_hash_map_.end()) {
if (i.key() >= r.in() && i.key() < r.out()) {
i = time_hash_map_.erase(i);
} else {
i++;
}
}
}
QString FrameHashCache::CachePathName(const QByteArray& hash, const PixelFormat::Format& pix_fmt)
{
QString ext = GetFormatExtension(pix_fmt);
@@ -184,7 +156,7 @@ QString VideoRenderFrameCache::CachePathName(const QByteArray& hash, const Pixel
return cache_dir.filePath(filename);
}
bool VideoRenderFrameCache::SaveCacheFrame(const QString &filename, char *data, const VideoRenderingParams &vparam)
bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoRenderingParams &vparam)
{
switch (vparam.format()) {
case PixelFormat::PIX_FMT_RGB8:
@@ -24,47 +24,29 @@
#include <QMutex>
#include "common/rational.h"
#include "common/timerange.h"
#include "render/pixelformat.h"
#include "render/playbackcache.h"
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
class VideoRenderFrameCache
class FrameHashCache : public PlaybackCache
{
Q_OBJECT
public:
VideoRenderFrameCache() = default;
FrameHashCache() = default;
void Clear();
/**
* @brief Return whether a frame with this hash already exists
*/
bool HasHash(const QByteArray& hash, const PixelFormat::Format &format);
/**
* @brief Return whether a frame is currently being cached
*/
bool IsCaching(const QByteArray& hash);
/**
* @brief Check if a frame is currently being cached, and if not reserve it
*/
bool TryCache(const QByteArray& hash);
void SetCacheID(const QString& id);
QByteArray TimeToHash(const rational& time) const;
QByteArray GetHash(const rational& time) const;
void SetHash(const rational& time, const QByteArray& hash);
void Truncate(const rational& time);
void RemoveHashFromCurrentlyCaching(const QByteArray& hash);
void SetTimebase(const rational& tb);
/**
* @brief Returns a list of frames that use a particular hash
*/
QList<rational> FramesWithHash(const QByteArray& hash) const;
QList<rational> GetFramesWithHash(const QByteArray& hash) const;
/**
* @brief Same as FramesWithHash() but also removes these frames from the map
@@ -73,23 +55,26 @@ public:
const QMap<rational, QByteArray>& time_hash_map() const;
static QString GetFormatExtension(const PixelFormat::Format& f);
/**
* @brief Return the path of the cached image at this time
*/
QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt) const;
static QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt);
static bool SaveCacheFrame(const QString& filename, char *data, const VideoRenderingParams &vparam);
void SaveCacheFrame(const QByteArray& hash, char *data, const VideoRenderingParams &vparam) const;
static void SaveCacheFrame(const QByteArray& hash, char *data, const VideoRenderingParams &vparam);
static QString GetFormatExtension(const PixelFormat::Format& f);
protected:
virtual void LengthChangedEvent(const rational& old, const rational& newlen) override;
virtual void InvalidateEvent(const TimeRange& range) override;
private:
QMap<rational, QByteArray> time_hash_map_;
QMutex currently_caching_lock_;
QVector<QByteArray> currently_caching_list_;
rational timebase_;
QString cache_id_;
};
OLIVE_NAMESPACE_EXIT
+84
View File
@@ -0,0 +1,84 @@
/***
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 "playbackcache.h"
OLIVE_NAMESPACE_ENTER
PlaybackCache::PlaybackCache()
{
}
void PlaybackCache::Invalidate(const TimeRange &r)
{
invalidated_.InsertTimeRange(r);
InvalidateEvent(r);
emit Invalidated(r);
}
void PlaybackCache::SetLength(const rational &r)
{
if (length_ == r) {
// Same length - do nothing
return;
}
if (r > length_) {
// If new length is greater, simply extend the invalidated range for now
invalidated_.InsertTimeRange(TimeRange(r, length_));
} else {
// If new length is smaller, removed hashes
invalidated_.RemoveTimeRange(TimeRange(length_, r));
}
LengthChangedEvent(length_, r);
length_ = r;
}
bool PlaybackCache::IsFullyValidated() const
{
return invalidated_.isEmpty();
}
void PlaybackCache::Validate(const TimeRange &r)
{
invalidated_.RemoveTimeRange(r);
emit Validated(r);
}
void PlaybackCache::InvalidateAll()
{
Invalidate(TimeRange(0, length_));
}
void PlaybackCache::LengthChangedEvent(const rational &, const rational &)
{
}
void PlaybackCache::InvalidateEvent(const TimeRange &)
{
}
OLIVE_NAMESPACE_EXIT
@@ -18,27 +18,55 @@
***/
#ifndef AUDIOWORKER_H
#define AUDIOWORKER_H
#ifndef PLAYBACKCACHE_H
#define PLAYBACKCACHE_H
#include "../audiorenderworker.h"
#include <QObject>
#include "common/timerange.h"
OLIVE_NAMESPACE_ENTER
class AudioWorker : public AudioRenderWorker
class PlaybackCache : public QObject
{
Q_OBJECT
public:
AudioWorker(QHash<Node*, Node*>* copy_map, QObject* parent = nullptr);
PlaybackCache();
void Invalidate(const TimeRange& r);
void SetLength(const rational& r);
bool IsFullyValidated() const;
const TimeRangeList& GetInvalidatedRanges() const
{
return invalidated_;
}
signals:
void Invalidated(const TimeRange& r);
void Validated(const TimeRange& r);
void LengthChanged(const rational& r);
protected:
virtual NodeValue FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range) override;
void Validate(const TimeRange& r);
virtual void RunNodeAccelerated(const Node *node, const TimeRange& range, NodeValueDatabase& input_params, NodeValueTable& output_params) override;
void InvalidateAll();
virtual void LengthChangedEvent(const rational& old, const rational& newlen);
virtual void InvalidateEvent(const TimeRange& range);
private:
TimeRangeList invalidated_;
rational length_;
};
OLIVE_NAMESPACE_EXIT
#endif // AUDIOWORKER_H
#endif // PLAYBACKCACHE_H