updated exporter to work with new frame flow
This commit is contained in:
@@ -96,7 +96,7 @@ void Encoder::Open()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Encoder::Write(FramePtr frame)
|
void Encoder::WriteFrame(FramePtr frame)
|
||||||
{
|
{
|
||||||
if (open_) {
|
if (open_) {
|
||||||
WriteInternal(frame);
|
WriteInternal(frame);
|
||||||
|
|||||||
+3
-1
@@ -61,7 +61,7 @@ public:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void Open();
|
void Open();
|
||||||
void Write(FramePtr frame);
|
void WriteFrame(FramePtr frame);
|
||||||
virtual void WriteAudio(const AudioRenderingParams& pcm_info, const QString& pcm_filename) = 0;
|
virtual void WriteAudio(const AudioRenderingParams& pcm_info, const QString& pcm_filename) = 0;
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
@@ -71,6 +71,8 @@ signals:
|
|||||||
|
|
||||||
void Closed();
|
void Closed();
|
||||||
|
|
||||||
|
void AudioComplete();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool OpenInternal() = 0;
|
virtual bool OpenInternal() = 0;
|
||||||
virtual void WriteInternal(FramePtr frame) = 0;
|
virtual void WriteInternal(FramePtr frame) = 0;
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ void FFmpegEncoder::WriteAudio(const AudioRenderingParams &pcm_info, const QStri
|
|||||||
|
|
||||||
pcm.close();
|
pcm.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit AudioComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FFmpegEncoder::OpenInternal()
|
bool FFmpegEncoder::OpenInternal()
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ Exporter::Exporter(ViewerOutput* viewer,
|
|||||||
color_processor_(color_processor),
|
color_processor_(color_processor),
|
||||||
encoder_(encoder),
|
encoder_(encoder),
|
||||||
export_status_(false),
|
export_status_(false),
|
||||||
export_msg_(tr("Export hasn't started yet"))
|
export_msg_(tr("Export hasn't started yet")),
|
||||||
|
video_done_(false),
|
||||||
|
audio_done_(false)
|
||||||
{
|
{
|
||||||
connect(this, &Exporter::ExportEnded, this, &Exporter::deleteLater);
|
connect(this, &Exporter::ExportEnded, this, &Exporter::deleteLater);
|
||||||
}
|
}
|
||||||
@@ -53,20 +55,16 @@ void Exporter::StartExporting()
|
|||||||
video_params_.time_base(),
|
video_params_.time_base(),
|
||||||
video_params_.format(),
|
video_params_.format(),
|
||||||
video_params_.mode()));
|
video_params_.mode()));
|
||||||
video_backend_->SetExportMode(true);
|
|
||||||
audio_backend_->SetViewerNode(viewer_node_);
|
audio_backend_->SetViewerNode(viewer_node_);
|
||||||
audio_backend_->SetParameters(audio_params_);
|
audio_backend_->SetParameters(audio_params_);
|
||||||
|
|
||||||
// Connect to renderers
|
|
||||||
connect(video_backend_, &VideoRenderBackend::CachedFrameReady, this, &Exporter::FrameRendered);
|
|
||||||
connect(audio_backend_, &AudioRenderBackend::QueueComplete, this, &Exporter::AudioRendered);
|
|
||||||
|
|
||||||
// Create renderers
|
// Create renderers
|
||||||
waiting_for_frame_ = 0;
|
waiting_for_frame_ = 0;
|
||||||
|
|
||||||
// Open encoder and wait for result
|
// Open encoder and wait for result
|
||||||
connect(encoder_, &Encoder::OpenSucceeded, this, &Exporter::EncoderOpenedSuccessfully, Qt::QueuedConnection);
|
connect(encoder_, &Encoder::OpenSucceeded, this, &Exporter::EncoderOpenedSuccessfully, Qt::QueuedConnection);
|
||||||
connect(encoder_, &Encoder::OpenFailed, this, &Exporter::EncoderOpenFailed, Qt::QueuedConnection);
|
connect(encoder_, &Encoder::OpenFailed, this, &Exporter::EncoderOpenFailed, Qt::QueuedConnection);
|
||||||
|
connect(encoder_, &Encoder::AudioComplete, this, &Exporter::AudioEncodeComplete, Qt::QueuedConnection);
|
||||||
connect(encoder_, &Encoder::Closed, encoder_, &Encoder::deleteLater, Qt::QueuedConnection);
|
connect(encoder_, &Encoder::Closed, encoder_, &Encoder::deleteLater, Qt::QueuedConnection);
|
||||||
|
|
||||||
QMetaObject::invokeMethod(encoder_,
|
QMetaObject::invokeMethod(encoder_,
|
||||||
@@ -81,6 +79,10 @@ void Exporter::SetExportMessage(const QString &s)
|
|||||||
|
|
||||||
void Exporter::ExportSucceeded()
|
void Exporter::ExportSucceeded()
|
||||||
{
|
{
|
||||||
|
if (!audio_done_ || !video_done_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Cleanup();
|
Cleanup();
|
||||||
|
|
||||||
video_backend_->deleteLater();
|
video_backend_->deleteLater();
|
||||||
@@ -99,10 +101,8 @@ void Exporter::ExportFailed()
|
|||||||
emit ExportEnded();
|
emit ExportEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Exporter::FrameRendered(const rational &time, QVariant value)
|
void Exporter::EncodeFrame(const rational &time, QVariant value)
|
||||||
{
|
{
|
||||||
qDebug() << "Received" << time.toDouble() << "- waiting for" << waiting_for_frame_.toDouble();
|
|
||||||
|
|
||||||
if (time == waiting_for_frame_) {
|
if (time == waiting_for_frame_) {
|
||||||
bool get_cached = false;
|
bool get_cached = false;
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ void Exporter::FrameRendered(const rational &time, QVariant value)
|
|||||||
|
|
||||||
// Encode (may require re-associating alpha?)
|
// Encode (may require re-associating alpha?)
|
||||||
QMetaObject::invokeMethod(encoder_,
|
QMetaObject::invokeMethod(encoder_,
|
||||||
"Write",
|
"WriteFrame",
|
||||||
Qt::QueuedConnection,
|
Qt::QueuedConnection,
|
||||||
Q_ARG(FramePtr, frame));
|
Q_ARG(FramePtr, frame));
|
||||||
|
|
||||||
@@ -145,6 +145,8 @@ void Exporter::FrameRendered(const rational &time, QVariant value)
|
|||||||
} while (cached_frames_.contains(waiting_for_frame_));
|
} while (cached_frames_.contains(waiting_for_frame_));
|
||||||
|
|
||||||
if (waiting_for_frame_ >= viewer_node_->Length()) {
|
if (waiting_for_frame_ >= viewer_node_->Length()) {
|
||||||
|
video_done_ = true;
|
||||||
|
|
||||||
ExportSucceeded();
|
ExportSucceeded();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -152,6 +154,23 @@ void Exporter::FrameRendered(const rational &time, QVariant value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Exporter::FrameRendered(const rational &time, QVariant value)
|
||||||
|
{
|
||||||
|
qDebug() << "Received" << time.toDouble() << "- waiting for" << waiting_for_frame_.toDouble();
|
||||||
|
|
||||||
|
const QMap<rational, QByteArray>& time_hash_map = video_backend_->frame_cache()->time_hash_map();
|
||||||
|
|
||||||
|
QByteArray map_hash = time_hash_map.value(time);
|
||||||
|
|
||||||
|
EncodeFrame(time, value);
|
||||||
|
|
||||||
|
QList<rational> matching_times = matched_frames_.value(map_hash);
|
||||||
|
foreach (const rational& t, matching_times) {
|
||||||
|
qDebug() << " Also matches" << t;
|
||||||
|
EncodeFrame(t, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Exporter::AudioRendered()
|
void Exporter::AudioRendered()
|
||||||
{
|
{
|
||||||
// Retrieve the audio filename
|
// Retrieve the audio filename
|
||||||
@@ -167,14 +186,28 @@ void Exporter::AudioRendered()
|
|||||||
audio_backend_->deleteLater();
|
audio_backend_->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Exporter::AudioEncodeComplete()
|
||||||
|
{
|
||||||
|
audio_done_ = true;
|
||||||
|
|
||||||
|
ExportSucceeded();
|
||||||
|
}
|
||||||
|
|
||||||
void Exporter::EncoderOpenedSuccessfully()
|
void Exporter::EncoderOpenedSuccessfully()
|
||||||
{
|
{
|
||||||
// Invalidate caches
|
// Invalidate caches
|
||||||
if (encoder_->params().video_enabled()) {
|
if (encoder_->params().video_enabled()) {
|
||||||
|
// First we generate the hashes so we know exactly how many frames we need
|
||||||
|
video_backend_->SetOperatingMode(VideoRenderWorker::kHashOnly);
|
||||||
|
connect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
|
||||||
|
|
||||||
video_backend_->InvalidateCache(0, viewer_node_->Length());
|
video_backend_->InvalidateCache(0, viewer_node_->Length());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encoder_->params().audio_enabled()) {
|
if (encoder_->params().audio_enabled()) {
|
||||||
|
// We set the audio backend to render the full sequence to the disk
|
||||||
|
connect(audio_backend_, &AudioRenderBackend::QueueComplete, this, &Exporter::AudioRendered);
|
||||||
|
|
||||||
audio_backend_->InvalidateCache(0, viewer_node_->Length());
|
audio_backend_->InvalidateCache(0, viewer_node_->Length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,3 +223,49 @@ void Exporter::EncoderClosed()
|
|||||||
emit ProgressChanged(100);
|
emit ProgressChanged(100);
|
||||||
emit ExportEnded();
|
emit ExportEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
|
||||||
|
QMap<rational, QByteArray>::const_iterator i;
|
||||||
|
QMap<rational, QByteArray>::iterator j;
|
||||||
|
|
||||||
|
// Copy time hash map
|
||||||
|
QMap<rational, QByteArray> time_hash_map = video_backend_->frame_cache()->time_hash_map();
|
||||||
|
|
||||||
|
// Check for any times that share duplicate hashes
|
||||||
|
for (i=time_hash_map.begin();i!=time_hash_map.end();i++) {
|
||||||
|
j = time_hash_map.begin();
|
||||||
|
|
||||||
|
while (j != time_hash_map.end()) {
|
||||||
|
if (i != j && i.value() == j.value()) {
|
||||||
|
// Remove the time range from this and
|
||||||
|
ranges.RemoveTimeRange(TimeRange(j.key(), j.key() + video_backend_->params().time_base()));
|
||||||
|
|
||||||
|
QList<rational> times_with_this_hash = matched_frames_.value(i.value());
|
||||||
|
times_with_this_hash.append(j.key());
|
||||||
|
matched_frames_.insert(i.value(), times_with_this_hash);
|
||||||
|
|
||||||
|
j = time_hash_map.erase(j);
|
||||||
|
} else {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set video backend to render mode but NOT hash or download
|
||||||
|
video_backend_->SetOperatingMode(VideoRenderWorker::kRenderOnly);
|
||||||
|
video_backend_->SetOnlySignalLastFrameRequested(false);
|
||||||
|
|
||||||
|
connect(video_backend_, &VideoRenderBackend::CachedFrameReady, this, &Exporter::FrameRendered);
|
||||||
|
|
||||||
|
foreach (const TimeRange& range, ranges) {
|
||||||
|
video_backend_->InvalidateCache(range.in(), range.out());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ private:
|
|||||||
|
|
||||||
void ExportFailed();
|
void ExportFailed();
|
||||||
|
|
||||||
|
void EncodeFrame(const rational &time, QVariant value);
|
||||||
|
|
||||||
ColorProcessorPtr color_processor_;
|
ColorProcessorPtr color_processor_;
|
||||||
|
|
||||||
Encoder* encoder_;
|
Encoder* encoder_;
|
||||||
@@ -73,17 +75,27 @@ private:
|
|||||||
|
|
||||||
QHash<rational, QVariant> cached_frames_;
|
QHash<rational, QVariant> cached_frames_;
|
||||||
|
|
||||||
|
QHash< QByteArray, QList<rational> > matched_frames_;
|
||||||
|
|
||||||
|
bool video_done_;
|
||||||
|
|
||||||
|
bool audio_done_;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void FrameRendered(const rational& time, QVariant value);
|
void FrameRendered(const rational& time, QVariant value);
|
||||||
|
|
||||||
void AudioRendered();
|
void AudioRendered();
|
||||||
|
|
||||||
|
void AudioEncodeComplete();
|
||||||
|
|
||||||
void EncoderOpenedSuccessfully();
|
void EncoderOpenedSuccessfully();
|
||||||
|
|
||||||
void EncoderOpenFailed();
|
void EncoderOpenFailed();
|
||||||
|
|
||||||
void EncoderClosed();
|
void EncoderClosed();
|
||||||
|
|
||||||
|
void VideoHashesComplete();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // EXPORTER_H
|
#endif // EXPORTER_H
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ void OpenGLBackend::DecompileInternal()
|
|||||||
shader_cache_.Clear();
|
shader_cache_.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLBackend::EmitCachedFrameReady(const QList<rational> ×, const QVariant &value, qint64 job_time)
|
void OpenGLBackend::EmitCachedFrameReady(const rational &time, const QVariant &value, qint64 job_time)
|
||||||
{
|
{
|
||||||
OpenGLTextureCache::ReferencePtr ref = value.value<OpenGLTextureCache::ReferencePtr>();
|
OpenGLTextureCache::ReferencePtr ref = value.value<OpenGLTextureCache::ReferencePtr>();
|
||||||
OpenGLTexturePtr tex;
|
OpenGLTexturePtr tex;
|
||||||
@@ -152,9 +152,7 @@ void OpenGLBackend::EmitCachedFrameReady(const QList<rational> ×, const QVa
|
|||||||
tex = nullptr;
|
tex = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (const rational& t, times) {
|
emit CachedFrameReady(time, QVariant::fromValue(tex), job_time);
|
||||||
emit CachedFrameReady(t, QVariant::fromValue(tex), job_time);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenGLTexturePtr OpenGLBackend::CopyTexture(OpenGLTexturePtr input)
|
OpenGLTexturePtr OpenGLBackend::CopyTexture(OpenGLTexturePtr input)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ protected:
|
|||||||
|
|
||||||
virtual void DecompileInternal() override;
|
virtual void DecompileInternal() override;
|
||||||
|
|
||||||
virtual void EmitCachedFrameReady(const QList<rational> ×, const QVariant& value, qint64 job_time) override;
|
virtual void EmitCachedFrameReady(const rational &time, const QVariant& value, qint64 job_time) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OpenGLTexturePtr CopyTexture(OpenGLTexturePtr input);
|
OpenGLTexturePtr CopyTexture(OpenGLTexturePtr input);
|
||||||
|
|||||||
@@ -237,7 +237,10 @@ void RenderBackend::CacheNext()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cache_queue_.isEmpty()) {
|
if (cache_queue_.isEmpty()) {
|
||||||
emit QueueComplete();
|
if (AllProcessorsAreAvailable()) {
|
||||||
|
emit QueueComplete();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ protected:
|
|||||||
|
|
||||||
void QueueValueUpdate();
|
void QueueValueUpdate();
|
||||||
|
|
||||||
|
bool AllProcessorsAreAvailable() const;
|
||||||
bool WorkerIsBusy(RenderWorker* worker) const;
|
bool WorkerIsBusy(RenderWorker* worker) const;
|
||||||
void SetWorkerBusyState(RenderWorker* worker, bool busy);
|
void SetWorkerBusyState(RenderWorker* worker, bool busy);
|
||||||
|
|
||||||
@@ -104,8 +105,6 @@ protected slots:
|
|||||||
void QueueRecompile();
|
void QueueRecompile();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool AllProcessorsAreAvailable() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Internal list of RenderProcessThreads
|
* @brief Internal list of RenderProcessThreads
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
|
|
||||||
VideoRenderBackend::VideoRenderBackend(QObject *parent) :
|
VideoRenderBackend::VideoRenderBackend(QObject *parent) :
|
||||||
RenderBackend(parent),
|
RenderBackend(parent),
|
||||||
export_mode_(false)
|
operating_mode_(VideoRenderWorker::kHashRenderCache),
|
||||||
|
only_signal_last_frame_requested_(true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,12 +72,15 @@ const VideoRenderingParams &VideoRenderBackend::params() const
|
|||||||
|
|
||||||
void VideoRenderBackend::SetParameters(const VideoRenderingParams& params)
|
void VideoRenderBackend::SetParameters(const VideoRenderingParams& params)
|
||||||
{
|
{
|
||||||
|
if (!AllProcessorsAreAvailable()) {
|
||||||
|
qCritical() << "Attempted to set parameters on a backend whose workers are still busy";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set new parameters
|
// Set new parameters
|
||||||
params_ = params;
|
params_ = params;
|
||||||
|
|
||||||
// Set params on all processors
|
// Set params on all processors
|
||||||
// FIXME: Undefined behavior if the processors are currently working, this may need to be delayed like the
|
|
||||||
// recompile signal
|
|
||||||
foreach (RenderWorker* worker, processors_) {
|
foreach (RenderWorker* worker, processors_) {
|
||||||
static_cast<VideoRenderWorker*>(worker)->SetParameters(params_);
|
static_cast<VideoRenderWorker*>(worker)->SetParameters(params_);
|
||||||
}
|
}
|
||||||
@@ -85,9 +89,23 @@ void VideoRenderBackend::SetParameters(const VideoRenderingParams& params)
|
|||||||
RegenerateCacheID();
|
RegenerateCacheID();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoRenderBackend::SetExportMode(bool enabled)
|
void VideoRenderBackend::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode)
|
||||||
{
|
{
|
||||||
export_mode_ = enabled;
|
if (!AllProcessorsAreAvailable()) {
|
||||||
|
qCritical() << "Attempted to set operating mode on a backend whose workers are still busy";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
operating_mode_ = mode;
|
||||||
|
|
||||||
|
foreach (RenderWorker* worker, processors_) {
|
||||||
|
static_cast<VideoRenderWorker*>(worker)->SetOperatingMode(operating_mode_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoRenderBackend::SetOnlySignalLastFrameRequested(bool enabled)
|
||||||
|
{
|
||||||
|
only_signal_last_frame_requested_ = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoRenderBackend::IsRendered(const rational &time) const
|
bool VideoRenderBackend::IsRendered(const rational &time) const
|
||||||
@@ -121,6 +139,8 @@ void VideoRenderBackend::ConnectWorkerToThis(RenderWorker *processor)
|
|||||||
{
|
{
|
||||||
VideoRenderWorker* video_processor = static_cast<VideoRenderWorker*>(processor);
|
VideoRenderWorker* video_processor = static_cast<VideoRenderWorker*>(processor);
|
||||||
|
|
||||||
|
video_processor->SetOperatingMode(operating_mode_);
|
||||||
|
|
||||||
connect(video_processor, &VideoRenderWorker::CompletedFrame, this, &VideoRenderBackend::ThreadCompletedFrame);
|
connect(video_processor, &VideoRenderWorker::CompletedFrame, this, &VideoRenderBackend::ThreadCompletedFrame);
|
||||||
connect(video_processor, &VideoRenderWorker::HashAlreadyBeingCached, this, &VideoRenderBackend::ThreadSkippedFrame);
|
connect(video_processor, &VideoRenderWorker::HashAlreadyBeingCached, this, &VideoRenderBackend::ThreadSkippedFrame);
|
||||||
connect(video_processor, &VideoRenderWorker::CompletedDownload, this, &VideoRenderBackend::ThreadCompletedDownload);
|
connect(video_processor, &VideoRenderWorker::CompletedDownload, this, &VideoRenderBackend::ThreadCompletedDownload);
|
||||||
@@ -206,8 +226,15 @@ TimeRange VideoRenderBackend::PopNextFrameFromQueue()
|
|||||||
|
|
||||||
void VideoRenderBackend::ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value)
|
void VideoRenderBackend::ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value)
|
||||||
{
|
{
|
||||||
if (last_time_requested_ == path.in() || frame_cache_.TimeToHash(last_time_requested_) == hash) {
|
if (!only_signal_last_frame_requested_ || last_time_requested_ == path.in() || frame_cache_.TimeToHash(last_time_requested_) == hash) {
|
||||||
EmitCachedFrameReady({last_time_requested_}, value, job_time);
|
EmitCachedFrameReady(path.in(), value, job_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(operating_mode_ & VideoRenderWorker::kDownloadOnly)) {
|
||||||
|
// If we're not downloading, the worker is done here
|
||||||
|
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
|
||||||
|
|
||||||
|
CacheNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "render/pixelformat.h"
|
#include "render/pixelformat.h"
|
||||||
#include "render/rendermodes.h"
|
#include "render/rendermodes.h"
|
||||||
#include "videorenderframecache.h"
|
#include "videorenderframecache.h"
|
||||||
|
#include "videorenderworker.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A multithreaded OpenGL based renderer for node systems
|
* @brief A multithreaded OpenGL based renderer for node systems
|
||||||
@@ -53,10 +54,16 @@ public:
|
|||||||
*/
|
*/
|
||||||
void SetParameters(const VideoRenderingParams ¶ms);
|
void SetParameters(const VideoRenderingParams ¶ms);
|
||||||
|
|
||||||
void SetExportMode(bool enabled);
|
void SetOperatingMode(const VideoRenderWorker::OperatingMode& mode);
|
||||||
|
|
||||||
|
void SetOnlySignalLastFrameRequested(bool enabled);
|
||||||
|
|
||||||
bool IsRendered(const rational& time) const;
|
bool IsRendered(const rational& time) const;
|
||||||
|
|
||||||
|
VideoRenderFrameCache* frame_cache();
|
||||||
|
|
||||||
|
const VideoRenderingParams& params() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* @brief Allocate and start the multithreaded backend
|
* @brief Allocate and start the multithreaded backend
|
||||||
@@ -85,10 +92,6 @@ protected:
|
|||||||
|
|
||||||
virtual TimeRange PopNextFrameFromQueue() override;
|
virtual TimeRange PopNextFrameFromQueue() override;
|
||||||
|
|
||||||
VideoRenderFrameCache* frame_cache();
|
|
||||||
|
|
||||||
const VideoRenderingParams& params() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Internal function for generating the cache ID
|
* @brief Internal function for generating the cache ID
|
||||||
*/
|
*/
|
||||||
@@ -98,9 +101,9 @@ protected:
|
|||||||
|
|
||||||
virtual void ConnectWorkerToThis(RenderWorker* processor) override;
|
virtual void ConnectWorkerToThis(RenderWorker* processor) override;
|
||||||
|
|
||||||
virtual void EmitCachedFrameReady(const QList<rational> ×, const QVariant& value, qint64 job_time) = 0;
|
virtual void EmitCachedFrameReady(const rational &time, const QVariant& value, qint64 job_time) = 0;
|
||||||
|
|
||||||
bool export_mode_;
|
VideoRenderWorker::OperatingMode operating_mode_;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void CachedFrameReady(const rational& time, QVariant value, qint64 job_time);
|
void CachedFrameReady(const rational& time, QVariant value, qint64 job_time);
|
||||||
@@ -121,6 +124,8 @@ private:
|
|||||||
|
|
||||||
rational last_time_requested_;
|
rational last_time_requested_;
|
||||||
|
|
||||||
|
bool only_signal_last_frame_requested_;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value);
|
void ThreadCompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value);
|
||||||
void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash);
|
void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash);
|
||||||
|
|||||||
@@ -109,6 +109,11 @@ QList<rational> VideoRenderFrameCache::FramesWithHash(const QByteArray &hash)
|
|||||||
return times;
|
return times;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QMap<rational, QByteArray> &VideoRenderFrameCache::time_hash_map() const
|
||||||
|
{
|
||||||
|
return time_hash_map_;
|
||||||
|
}
|
||||||
|
|
||||||
QString VideoRenderFrameCache::CachePathName(const QByteArray &hash) const
|
QString VideoRenderFrameCache::CachePathName(const QByteArray &hash) const
|
||||||
{
|
{
|
||||||
QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id_);
|
QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id_);
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public:
|
|||||||
|
|
||||||
QList<rational> FramesWithHash(const QByteArray& hash);
|
QList<rational> FramesWithHash(const QByteArray& hash);
|
||||||
|
|
||||||
|
const QMap<rational, QByteArray>& time_hash_map() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMap<rational, QByteArray> time_hash_map_;
|
QMap<rational, QByteArray> time_hash_map_;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, QObject *parent) :
|
VideoRenderWorker::VideoRenderWorker(VideoRenderFrameCache *frame_cache, QObject *parent) :
|
||||||
RenderWorker(parent),
|
RenderWorker(parent),
|
||||||
frame_cache_(frame_cache)
|
frame_cache_(frame_cache),
|
||||||
|
operating_mode_(kHashRenderCache)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,16 +23,27 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con
|
|||||||
{
|
{
|
||||||
// Get hash of node graph
|
// Get hash of node graph
|
||||||
// We use SHA-1 for speed (benchmarks show it's the fastest hash available to us)
|
// We use SHA-1 for speed (benchmarks show it's the fastest hash available to us)
|
||||||
QCryptographicHash hasher(QCryptographicHash::Sha1);
|
QByteArray hash;
|
||||||
HashNodeRecursively(&hasher, path.node(), path.in());
|
if (operating_mode_ & kHashOnly) {
|
||||||
QByteArray hash = hasher.result();
|
QCryptographicHash hasher(QCryptographicHash::Sha1);
|
||||||
|
HashNodeRecursively(&hasher, path.node(), path.in());
|
||||||
|
hash = hasher.result();
|
||||||
|
}
|
||||||
|
|
||||||
NodeValueTable value;
|
NodeValueTable value;
|
||||||
|
|
||||||
if (frame_cache_->HasHash(hash)) {
|
if (!(operating_mode_ & kRenderOnly)) {
|
||||||
|
|
||||||
|
// Emit only the hash
|
||||||
|
emit CompletedDownload(path, job_time, hash);
|
||||||
|
|
||||||
|
} else if ((operating_mode_ & kHashOnly) && frame_cache_->HasHash(hash)) {
|
||||||
|
|
||||||
// We've already cached this hash, no need to continue
|
// We've already cached this hash, no need to continue
|
||||||
emit HashAlreadyExists(path, job_time, hash);
|
emit HashAlreadyExists(path, job_time, hash);
|
||||||
} else if (frame_cache_->TryCache(path.in(), hash)) {
|
|
||||||
|
} else if (!(operating_mode_ & kHashOnly) || frame_cache_->TryCache(path.in(), hash)) {
|
||||||
|
|
||||||
// This hash is available for us to cache, start traversing graph
|
// This hash is available for us to cache, start traversing graph
|
||||||
value = ProcessNode(path);
|
value = ProcessNode(path);
|
||||||
|
|
||||||
@@ -42,17 +54,22 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con
|
|||||||
emit CompletedFrame(path, job_time, hash, texture);
|
emit CompletedFrame(path, job_time, hash, texture);
|
||||||
|
|
||||||
// If we actually have a texture, download it into the disk cache
|
// If we actually have a texture, download it into the disk cache
|
||||||
if (!texture.isNull()) {
|
if ((operating_mode_ & kDownloadOnly) && !texture.isNull()) {
|
||||||
Download(path, hash, texture, frame_cache_->CachePathName(hash));
|
Download(path, hash, texture, frame_cache_->CachePathName(hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
frame_cache_->RemoveHashFromCurrentlyCaching(hash);
|
frame_cache_->RemoveHashFromCurrentlyCaching(hash);
|
||||||
|
|
||||||
// Signal that this job is complete
|
if (operating_mode_ & kDownloadOnly) {
|
||||||
emit CompletedDownload(path, job_time, hash);
|
// Signal that this job is complete
|
||||||
|
emit CompletedDownload(path, job_time, hash);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Another thread must be caching this already, nothing to be done
|
// Another thread must be caching this already, nothing to be done
|
||||||
emit HashAlreadyBeingCached(path, job_time, hash);
|
emit HashAlreadyBeingCached(path, job_time, hash);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@@ -161,6 +178,11 @@ void VideoRenderWorker::SetParameters(const VideoRenderingParams &video_params)
|
|||||||
ParametersChangedEvent();
|
ParametersChangedEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VideoRenderWorker::SetOperatingMode(const VideoRenderWorker::OperatingMode &mode)
|
||||||
|
{
|
||||||
|
operating_mode_ = mode;
|
||||||
|
}
|
||||||
|
|
||||||
bool VideoRenderWorker::InitInternal()
|
bool VideoRenderWorker::InitInternal()
|
||||||
{
|
{
|
||||||
download_buffer_.resize(PixelService::GetBufferSize(video_params().format(), video_params().effective_width(), video_params().effective_height()));
|
download_buffer_.resize(PixelService::GetBufferSize(video_params().format(), video_params().effective_width(), video_params().effective_height()));
|
||||||
|
|||||||
@@ -12,10 +12,41 @@
|
|||||||
class VideoRenderWorker : public RenderWorker {
|
class VideoRenderWorker : public RenderWorker {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
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);
|
VideoRenderWorker(VideoRenderFrameCache* frame_cache, QObject* parent = nullptr);
|
||||||
|
|
||||||
void SetParameters(const VideoRenderingParams& video_params);
|
void SetParameters(const VideoRenderingParams& video_params);
|
||||||
|
|
||||||
|
void SetOperatingMode(const OperatingMode& mode);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void CompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value);
|
void CompletedFrame(NodeDependency path, qint64 job_time, QByteArray hash, QVariant value);
|
||||||
|
|
||||||
@@ -57,6 +88,8 @@ private:
|
|||||||
|
|
||||||
QByteArray download_buffer_;
|
QByteArray download_buffer_;
|
||||||
|
|
||||||
|
OperatingMode operating_mode_;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user