diff --git a/app/render/backend/CMakeLists.txt b/app/render/backend/CMakeLists.txt index 4df8c6e0c..ec218f040 100644 --- a/app/render/backend/CMakeLists.txt +++ b/app/render/backend/CMakeLists.txt @@ -21,6 +21,9 @@ add_subdirectory(vulkan) set(OLIVE_SOURCES ${OLIVE_SOURCES} + render/backend/exporter.h + render/backend/exporter.cpp + render/backend/renderbackend.h render/backend/renderbackend.cpp render/backend/renderworker.h diff --git a/app/render/backend/audiorenderbackend.cpp b/app/render/backend/audiorenderbackend.cpp index e9cdf3923..0a2e09522 100644 --- a/app/render/backend/audiorenderbackend.cpp +++ b/app/render/backend/audiorenderbackend.cpp @@ -55,9 +55,6 @@ void AudioRenderBackend::ConnectViewer(ViewerOutput *node) { connect(node, SIGNAL(AudioChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&))); connect(node, SIGNAL(AudioGraphChanged()), this, SLOT(QueueRecompile())); - - // FIXME: Hardcoded format, though I doubt this will change any time soon (maybe we'll shift to DBL at some point) - SetParameters(AudioRenderingParams(node->audio_params(), SAMPLE_FMT_FLT)); } void AudioRenderBackend::DisconnectViewer(ViewerOutput *node) @@ -117,3 +114,8 @@ QString AudioRenderBackend::CachePathName() return this_cache_dir.filePath(QStringLiteral("pcm")); } + +bool AudioRenderBackend::CanRender() +{ + return params_.is_valid(); +} diff --git a/app/render/backend/audiorenderbackend.h b/app/render/backend/audiorenderbackend.h index 1685a6b4f..c27ff78ba 100644 --- a/app/render/backend/audiorenderbackend.h +++ b/app/render/backend/audiorenderbackend.h @@ -39,6 +39,8 @@ protected: QString CachePathName(); + virtual bool CanRender() override; + private: void ValidateRanges(); diff --git a/app/render/backend/opengl/CMakeLists.txt b/app/render/backend/opengl/CMakeLists.txt index 38148afc4..a0fc0d6f3 100644 --- a/app/render/backend/opengl/CMakeLists.txt +++ b/app/render/backend/opengl/CMakeLists.txt @@ -22,6 +22,8 @@ set(OLIVE_SOURCES render/backend/opengl/openglbackend.cpp render/backend/opengl/openglcolorprocessor.h render/backend/opengl/openglcolorprocessor.cpp + render/backend/opengl/openglexporter.h + render/backend/opengl/openglexporter.cpp render/backend/opengl/openglframebuffer.h render/backend/opengl/openglframebuffer.cpp render/backend/opengl/openglshader.h diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/render/backend/opengl/openglbackend.cpp index f51c4e2d3..09fe1bd6d 100644 --- a/app/render/backend/opengl/openglbackend.cpp +++ b/app/render/backend/opengl/openglbackend.cpp @@ -6,8 +6,7 @@ #include "functions.h" OpenGLBackend::OpenGLBackend(QObject *parent) : - VideoRenderBackend(parent), - last_download_thread_(0) + VideoRenderBackend(parent) { } @@ -135,76 +134,15 @@ void OpenGLBackend::DecompileInternal() shader_cache_.Clear(); } -bool OpenGLBackend::TimeIsQueued(const TimeRange &time) +void OpenGLBackend::EmitCachedFrameReady(const rational &time, const QVariant &value) { - return cache_queue_.contains(time); -} + // FIXME: This texture is part of the texture cache and therefore volatile, we should probably copy it here instead + OpenGLTextureCache::ReferencePtr ref = value.value(); + OpenGLTexturePtr tex = nullptr; -void OpenGLBackend::ThreadCompletedFrame(NodeDependency path, QByteArray hash, NodeValueTable table) -{ - SetWorkerBusyState(static_cast(sender()), false); - - OpenGLTextureCache::ReferencePtr ref = table.Get(NodeParam::kTexture).value(); - - if (!ref) { - // No frame received, we set hash to an empty - frame_cache()->RemoveHash(path.in(), hash); - } else { - // Received a texture, let's download it - QString cache_fn = frame_cache()->CachePathName(hash); - - // Find an available worker to download this texture - QMetaObject::invokeMethod(processors_.at(last_download_thread_%processors_.size()), - "Download", - Q_ARG(NodeDependency, path), - Q_ARG(QByteArray, hash), - Q_ARG(QVariant, QVariant::fromValue(ref)), - Q_ARG(QString, cache_fn)); - - last_download_thread_++; + if (ref) { + tex = ref->texture(); } - // Check if this frame has changed once again, in which case we may not want to draw it (it'll look jittery to the user) - if (!TimeIsQueued(TimeRange(path.in(), path.in()))) { - // FIXME: This texture is part of the texture cache and therefore volatile, we should probably copy it here instead - OpenGLTexturePtr tex = nullptr; - - if (ref) { - tex = ref->texture(); - } - - emit CachedFrameReady(path.in(), QVariant::fromValue(tex)); - } - - // Queue up a new frame for this worker - CacheNext(); -} - -void OpenGLBackend::ThreadCompletedDownload(NodeDependency dep, QByteArray hash) -{ - frame_cache()->SetHash(dep.in(), hash); - - // Emit for each frame that has this hash (some may have been added in ThreadSkippedFrame) - QList times_with_this_hash = frame_cache()->TimesWithHash(hash); - foreach (const rational& t, times_with_this_hash) { - emit CachedTimeReady(t); - } -} - -void OpenGLBackend::ThreadSkippedFrame(NodeDependency dep, QByteArray hash) -{ - frame_cache()->SetHash(dep.in(), hash); - SetWorkerBusyState(static_cast(sender()), false); - - // Queue up a new frame for this worker - CacheNext(); -} - -void OpenGLBackend::ThreadHashAlreadyExists(NodeDependency dep, QByteArray hash) -{ - ThreadCompletedDownload(dep, hash); - SetWorkerBusyState(static_cast(sender()), false); - - // Queue up a new frame for this worker - CacheNext(); + emit CachedFrameReady(time, QVariant::fromValue(tex)); } diff --git a/app/render/backend/opengl/openglbackend.h b/app/render/backend/opengl/openglbackend.h index 6b2a75643..74f3feec5 100644 --- a/app/render/backend/opengl/openglbackend.h +++ b/app/render/backend/opengl/openglbackend.h @@ -28,23 +28,15 @@ protected: virtual void DecompileInternal() override; -private: - bool TimeIsQueued(const TimeRange &time); + virtual void EmitCachedFrameReady(const rational& time, const QVariant& value) override; +private: OpenGLTexturePtr master_texture_; OpenGLShaderCache shader_cache_; OpenGLTextureCache texture_cache_; - int last_download_thread_; - -private slots: - void ThreadCompletedFrame(NodeDependency path, QByteArray hash, NodeValueTable table); - void ThreadCompletedDownload(NodeDependency dep, QByteArray hash); - void ThreadSkippedFrame(NodeDependency dep, QByteArray hash); - void ThreadHashAlreadyExists(NodeDependency dep, QByteArray hash); - }; #endif // OPENGLBACKEND_H diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 1e1ff1e77..f5f978f3e 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -90,17 +90,11 @@ void RenderBackend::SetViewerNode(ViewerOutput *viewer_node) if (viewer_node_ != nullptr) { ConnectViewer(viewer_node_); + + RegenerateCacheID(); } } -void RenderBackend::SetCacheName(const QString &s) -{ - cache_name_ = s; - cache_time_ = QDateTime::currentMSecsSinceEpoch(); - - RegenerateCacheID(); -} - bool RenderBackend::IsInitiated() { return started_; @@ -170,22 +164,25 @@ void RenderBackend::RegenerateCacheID() { QCryptographicHash hash(QCryptographicHash::Sha1); - if (cache_name_.isEmpty() - || !cache_time_ + if (!viewer_node_ || !GenerateCacheIDInternal(hash)) { cache_id_.clear(); CacheIDChangedEvent(QString()); return; } - hash.addData(cache_name_.toUtf8()); - hash.addData(QString::number(cache_time_).toUtf8()); + hash.addData(viewer_node_->uuid().toByteArray()); QByteArray bytes = hash.result(); cache_id_ = bytes.toHex(); CacheIDChangedEvent(cache_id_); } +bool RenderBackend::CanRender() +{ + return true; +} + rational RenderBackend::GetSequenceLength() { if (viewer_node_ == nullptr) { @@ -214,7 +211,8 @@ void RenderBackend::CacheNext() { if (!Init() || cache_queue_.isEmpty() - || !ViewerIsConnected()) { + || !ViewerIsConnected() + || !CanRender()) { return; } diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index a55b74aa9..a14fafa14 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -25,10 +25,10 @@ public: void SetViewerNode(ViewerOutput* viewer_node); - void SetCacheName(const QString& s); - bool IsInitiated(); + ViewerOutput* viewer_node() const; + public slots: virtual void InvalidateCache(const rational &start_range, const rational &end_range); @@ -47,6 +47,8 @@ protected: virtual void DecompileInternal() = 0; + virtual bool CanRender(); + rational GetSequenceLength(); const QVector& threads(); @@ -76,8 +78,6 @@ protected: virtual void ConnectWorkerToThis(RenderWorker* worker) = 0; - ViewerOutput* viewer_node() const; - bool ViewerIsConnected() const; const QString& cache_id() const; @@ -121,8 +121,6 @@ private: */ QString error_; - QString cache_name_; - qint64 cache_time_; QString cache_id_; QList source_node_list_; diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 96a6ce248..1299f1f78 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -31,10 +31,10 @@ #include "videorenderworker.h" VideoRenderBackend::VideoRenderBackend(QObject *parent) : - RenderBackend(parent) + RenderBackend(parent), + export_mode_(false), + last_download_thread_(0) { - // FIXME: Cache name should actually be the name of the sequence - SetCacheName("Test"); } void VideoRenderBackend::InvalidateCache(const rational &start_range, const rational &end_range) @@ -122,9 +122,6 @@ void VideoRenderBackend::ConnectViewer(ViewerOutput *node) { connect(node, SIGNAL(VideoChangedBetween(const rational&, const rational&)), this, SLOT(InvalidateCache(const rational&, const rational&))); connect(node, SIGNAL(VideoGraphChanged()), this, SLOT(QueueRecompile())); - - // FIXME: Hardcoded format, mode, and divider - SetParameters(VideoRenderingParams(node->video_params(), olive::PIX_FMT_RGBA16F, olive::kOffline, 2)); } void VideoRenderBackend::DisconnectViewer(ViewerOutput *node) @@ -154,6 +151,11 @@ void VideoRenderBackend::SetParameters(const VideoRenderingParams& params) RegenerateCacheID(); } +void VideoRenderBackend::SetExportMode(bool enabled) +{ + export_mode_ = enabled; +} + bool VideoRenderBackend::GenerateCacheIDInternal(QCryptographicHash& hash) { if (!params_.is_valid()) { @@ -234,3 +236,84 @@ NodeInput *VideoRenderBackend::GetDependentInput() { return viewer_node()->texture_input(); } + +bool VideoRenderBackend::CanRender() +{ + return params_.is_valid(); +} + +void VideoRenderBackend::ThreadCompletedFrame(NodeDependency path, QByteArray hash, NodeValueTable table) +{ + SetWorkerBusyState(static_cast(sender()), false); + + QVariant texture = table.Get(NodeParam::kTexture); + + if (!export_mode_) { + if (texture.isNull()) { + // No frame received, we set hash to an empty + frame_cache()->RemoveHash(path.in(), hash); + } else { + // Received a texture, let's download it + QString cache_fn = frame_cache()->CachePathName(hash); + + // Find an available worker to download this texture + QMetaObject::invokeMethod(processors_.at(last_download_thread_%processors_.size()), + "Download", + Q_ARG(NodeDependency, path), + Q_ARG(QByteArray, hash), + Q_ARG(QVariant, texture), + Q_ARG(QString, cache_fn)); + + last_download_thread_++; + } + } + + // Check if this frame has changed once again, in which case we may not want to draw it (it'll look jittery to the user) + if (!TimeIsQueued(TimeRange(path.in(), path.in()))) { + EmitCachedFrameReady(path.in(), texture); + + if (export_mode_) { + QList times_with_this_hash = frame_cache()->TimesWithHash(hash); + foreach (const rational& t, times_with_this_hash) { + EmitCachedFrameReady(t, texture); + } + } + } + + // Queue up a new frame for this worker + CacheNext(); +} + +void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, QByteArray hash) +{ + frame_cache()->SetHash(dep.in(), hash); + + // Emit for each frame that has this hash (some may have been added in ThreadSkippedFrame) + QList times_with_this_hash = frame_cache()->TimesWithHash(hash); + foreach (const rational& t, times_with_this_hash) { + emit CachedTimeReady(t); + } +} + +void VideoRenderBackend::ThreadSkippedFrame(NodeDependency dep, QByteArray hash) +{ + frame_cache()->SetHash(dep.in(), hash); + SetWorkerBusyState(static_cast(sender()), false); + + // Queue up a new frame for this worker + CacheNext(); +} + +void VideoRenderBackend::ThreadHashAlreadyExists(NodeDependency dep, QByteArray hash) +{ + ThreadCompletedDownload(dep, hash); + SetWorkerBusyState(static_cast(sender()), false); + + // Queue up a new frame for this worker + CacheNext(); +} + +bool VideoRenderBackend::TimeIsQueued(const TimeRange &time) +{ + return cache_queue_.contains(time); +} diff --git a/app/render/backend/videorenderbackend.h b/app/render/backend/videorenderbackend.h index 975ab856c..7b9f8d7a3 100644 --- a/app/render/backend/videorenderbackend.h +++ b/app/render/backend/videorenderbackend.h @@ -53,6 +53,8 @@ public: */ void SetParameters(const VideoRenderingParams ¶ms); + void SetExportMode(bool enabled); + public slots: virtual void InvalidateCache(const rational &start_range, const rational &end_range) override; @@ -80,6 +82,8 @@ protected: virtual NodeInput* GetDependentInput() override; + virtual bool CanRender() override; + VideoRenderFrameCache* frame_cache(); const VideoRenderingParams& params() const; @@ -93,11 +97,17 @@ protected: virtual void ConnectWorkerToThis(RenderWorker* processor) override; + virtual void EmitCachedFrameReady(const rational& time, const QVariant& value) = 0; + + bool export_mode_; + signals: void CachedFrameReady(const rational& time, QVariant value); void CachedTimeReady(const rational& time); private: + bool TimeIsQueued(const TimeRange &time); + VideoRenderingParams params_; QByteArray cache_frame_load_buffer_; @@ -106,8 +116,13 @@ private: rational last_time_requested_; -private slots: + int last_download_thread_; +private slots: + void ThreadCompletedFrame(NodeDependency path, QByteArray hash, NodeValueTable table); + void ThreadCompletedDownload(NodeDependency dep, QByteArray hash); + void ThreadSkippedFrame(NodeDependency dep, QByteArray hash); + void ThreadHashAlreadyExists(NodeDependency dep, QByteArray hash); };