diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp
index fa5c8c1df..a13650fee 100644
--- a/app/node/output/viewer/viewer.cpp
+++ b/app/node/output/viewer/viewer.cpp
@@ -106,6 +106,8 @@ void ViewerOutput::set_video_params(const VideoParams &video)
{
video_params_ = video;
+ video_frame_cache_.SetTimebase(video_params_.time_base());
+
emit SizeChanged(video_params_.width(), video_params_.height());
emit TimebaseChanged(video_params_.time_base());
emit ParamsChanged();
diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp
index 070a53094..e83dd6169 100644
--- a/app/render/framehashcache.cpp
+++ b/app/render/framehashcache.cpp
@@ -116,6 +116,11 @@ void FrameHashCache::SaveCacheFrame(const QByteArray& hash,
}
}
+void FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame)
+{
+ SaveCacheFrame(hash, frame->data(), frame->video_params());
+}
+
void FrameHashCache::LengthChangedEvent(const rational &old, const rational &newlen)
{
if (newlen < old) {
@@ -174,8 +179,8 @@ bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const V
// Attempt to keep this write to one thread
out->threads(1);
- out->open(fn_std, OIIO::ImageSpec(vparam.width(),
- vparam.height(),
+ out->open(fn_std, OIIO::ImageSpec(vparam.effective_width(),
+ vparam.effective_height(),
PixelFormat::ChannelCount(vparam.format()),
PixelFormat::GetOIIOTypeDesc(vparam.format())));
diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h
index 343cfed0c..3c57310b8 100644
--- a/app/render/framehashcache.h
+++ b/app/render/framehashcache.h
@@ -62,6 +62,7 @@ public:
static bool SaveCacheFrame(const QString& filename, char *data, const VideoRenderingParams &vparam);
static void SaveCacheFrame(const QByteArray& hash, char *data, const VideoRenderingParams &vparam);
+ static void SaveCacheFrame(const QByteArray& hash, FramePtr frame);
static QString GetFormatExtension(const PixelFormat::Format& f);
diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h
index 1ade109b7..949d15e64 100644
--- a/app/render/playbackcache.h
+++ b/app/render/playbackcache.h
@@ -29,7 +29,7 @@ OLIVE_NAMESPACE_ENTER
class PlaybackCache : public QObject
{
- Q_OBJECT
+ Q_OBJECT
public:
PlaybackCache();
@@ -50,11 +50,11 @@ public:
}
signals:
- void Invalidated(const TimeRange& r);
+ void Invalidated(const OLIVE_NAMESPACE::TimeRange& r);
- void Validated(const TimeRange& r);
+ void Validated(const OLIVE_NAMESPACE::TimeRange& r);
- void LengthChanged(const rational& r);
+ void LengthChanged(const OLIVE_NAMESPACE::rational& r);
protected:
void Validate(const TimeRange& r);
diff --git a/app/task/CMakeLists.txt b/app/task/CMakeLists.txt
index e9d4fa410..1e472bf2b 100644
--- a/app/task/CMakeLists.txt
+++ b/app/task/CMakeLists.txt
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+add_subdirectory(cache)
add_subdirectory(conform)
add_subdirectory(proxy)
diff --git a/app/task/cache/CMakeLists.txt b/app/task/cache/CMakeLists.txt
new file mode 100644
index 000000000..85f683a5d
--- /dev/null
+++ b/app/task/cache/CMakeLists.txt
@@ -0,0 +1,22 @@
+# 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 .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ task/cache/cache.h
+ task/cache/cache.cpp
+ PARENT_SCOPE
+)
diff --git a/app/task/cache/cache.cpp b/app/task/cache/cache.cpp
new file mode 100644
index 000000000..9f34ec875
--- /dev/null
+++ b/app/task/cache/cache.cpp
@@ -0,0 +1,174 @@
+/***
+
+ 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 .
+
+***/
+
+#include "cache.h"
+
+#include
+
+#include "common/timecodefunctions.h"
+#include "project/item/sequence/sequence.h"
+#include "render/backend/opengl/openglbackend.h"
+
+OLIVE_NAMESPACE_ENTER
+
+CacheTask::CacheTask(ViewerOutput* viewer, int divider, bool in_out_only) :
+ viewer_(viewer),
+ in_out_only_(in_out_only),
+ divider_(divider)
+{
+ SetTitle(tr("Caching \"%1\"").arg(viewer_->media_name()));
+}
+
+struct TimeHashFuturePair {
+ rational time;
+ QFuture hash_future;
+};
+
+struct HashFrameFuturePair {
+ QByteArray hash;
+ QFuture frame_future;
+};
+
+struct HashDownloadFuturePair {
+ QByteArray hash;
+ QFuture download_future;
+};
+
+void CacheTask::Action()
+{
+ OpenGLBackend backend;
+
+ RenderMode::Mode mode = RenderMode::kOffline;
+ PixelFormat::Format format = PixelFormat::instance()->GetConfiguredFormatForMode(mode);
+
+ backend.SetViewerNode(viewer_);
+ backend.SetPixelFormat(format);
+ backend.SetMode(mode);
+ backend.SetDivider(divider_);
+ backend.SetSampleFormat(SampleFormat::kInternalFormat);
+
+ // Get list of invalidated ranges
+ TimeRangeList range_to_cache = viewer_->video_frame_cache()->GetInvalidatedRanges();
+
+ // If we're caching only in-out, limit the range to that
+ if (in_out_only_) {
+ Sequence* s = static_cast(viewer_->parent());
+
+ if (s->workarea()->enabled()) {
+ range_to_cache = range_to_cache.Intersects(s->workarea()->range());
+ }
+ }
+ // Get hashes for each frame
+ QLinkedList hash_list;
+
+ while (!range_to_cache.isEmpty()) {
+ const TimeRange& range = range_to_cache.first();
+
+ const rational& timebase = viewer_->video_params().time_base();
+
+ rational time = range.in();
+ rational snapped = Timecode::snap_time_to_timebase(time, timebase);
+ rational next;
+
+ if (snapped > time) {
+ next = snapped;
+ snapped -= timebase;
+ } else {
+ next = snapped + timebase;
+ }
+
+ hash_list.append({snapped, backend.Hash(snapped)});
+ range_to_cache.RemoveTimeRange(TimeRange(snapped, next));
+ }
+
+ // Determine any duplicates
+ QMap< QByteArray, QLinkedList > times_to_render;
+ foreach (const TimeHashFuturePair& i, hash_list) {
+ times_to_render[i.hash_future.result()].append(i.time);
+ }
+
+ // Render all frames necessary
+ QLinkedList render_lookup_table;
+ {
+ QMap< QByteArray, QLinkedList >::const_iterator i;
+ for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) {
+ const QByteArray& hash = i.key();
+
+ render_lookup_table.append({hash, backend.RenderFrame(i.value().first(), false)});
+ }
+ }
+
+ OIIO::TypeDesc output_desc = PixelFormat::GetOIIOTypeDesc(format);
+ OIIO::ImageSpec output_spec(viewer_->video_params().width() / divider_,
+ viewer_->video_params().height() / divider_,
+ PixelFormat::ChannelCount(format),
+ output_desc);
+
+ // Start downloading frames that have finished
+ {
+ int counter = 0;
+
+ QLinkedList download_futures;
+
+ // Iterators
+ QLinkedList::iterator i;
+ QLinkedList::iterator j;
+
+ while (!render_lookup_table.isEmpty() || !download_futures.isEmpty()) {
+ i = render_lookup_table.begin();
+
+ while (i != render_lookup_table.end()) {
+ if (i->frame_future.isFinished()) {
+ FramePtr f = i->frame_future.result();
+
+ // Start multithreaded download here
+ download_futures.append({i->hash,
+ QtConcurrent::run(FrameHashCache::SaveCacheFrame, i->hash, f)});
+
+ i = render_lookup_table.erase(i);
+ } else {
+ i++;
+ }
+ }
+
+ j = download_futures.begin();
+
+ while (j != download_futures.end()) {
+ if (j->download_future.isFinished()) {
+ // Place it in the cache
+ const QLinkedList& times_with_hash = times_to_render.value(j->hash);
+ foreach (const rational& t, times_with_hash) {
+ viewer_->video_frame_cache()->SetHash(t, j->hash);
+ }
+
+ // Signal process
+ counter++;
+ emit ProgressChanged(qRound(100.0 * static_cast(counter) / static_cast(render_lookup_table.size())));
+
+ j = download_futures.erase(j);
+ } else {
+ j++;
+ }
+ }
+ }
+ }
+}
+
+OLIVE_NAMESPACE_EXIT
diff --git a/app/task/cache/cache.h b/app/task/cache/cache.h
new file mode 100644
index 000000000..afaccb47a
--- /dev/null
+++ b/app/task/cache/cache.h
@@ -0,0 +1,49 @@
+/***
+
+ 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 .
+
+***/
+
+#ifndef CACHETASK_H
+#define CACHETASK_H
+
+#include "node/output/viewer/viewer.h"
+#include "task/task.h"
+
+OLIVE_NAMESPACE_ENTER
+
+class CacheTask : public Task
+{
+ Q_OBJECT
+public:
+ CacheTask(ViewerOutput* viewer, int divider, bool in_out_only);
+
+protected:
+ virtual void Action() override;
+
+private:
+ ViewerOutput* viewer_;
+
+ bool in_out_only_;
+
+ int divider_;
+
+};
+
+OLIVE_NAMESPACE_EXIT
+
+#endif // CACHETASK_H
diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp
index 4a8b32a8b..1d3c393cb 100644
--- a/app/widget/viewer/viewer.cpp
+++ b/app/widget/viewer/viewer.cpp
@@ -340,6 +340,45 @@ void ViewerWidget::SetGizmos(Node *node)
display_widget_->SetGizmos(node);
}
+FramePtr DecodeCachedImage(const QString &fn, const rational& time)
+{
+ FramePtr frame = nullptr;
+
+ if (!fn.isEmpty() && QFileInfo::exists(fn)) {
+ auto input = OIIO::ImageInput::open(fn.toStdString());
+
+ if (input) {
+
+ PixelFormat::Format image_format = PixelFormat::OIIOFormatToOliveFormat(input->spec().format,
+ input->spec().nchannels == kRGBAChannels);
+
+ frame = Frame::Create();
+ frame->set_timestamp(time);
+ frame->set_video_params(VideoRenderingParams(input->spec().width,
+ input->spec().height,
+ image_format));
+
+ frame->allocate();
+
+ input->read_image(input->spec().format,
+ frame->data(),
+ OIIO::AutoStride,
+ frame->linesize_bytes());
+
+ input->close();
+
+#if OIIO_VERSION < 10903
+ OIIO::ImageInput::destroy(input);
+#endif
+
+ } else {
+ qWarning() << "OIIO Error:" << OIIO::geterror().c_str();
+ }
+ }
+
+ return frame;
+}
+
void ViewerWidget::UpdateTextureFromNode(const rational& time)
{
if (!FrameExistsAtTime(time)) {
@@ -348,9 +387,8 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time)
return;
}
- {
- QMutexLocker locker(playback_queue_.lock());
-
+ // Check playback queue for a frame
+ if (IsPlaying()) {
while (!playback_queue_.isEmpty()) {
const ViewerPlaybackFrame& pf = playback_queue_.first();
@@ -368,13 +406,11 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time)
}
}
- }
- if (IsPlaying()) {
qDebug() << "Playback queue couldn't keep up - falling back to realtime decoding";
}
- // Frame was not in queue, will require decoding
+ // Frame was not in queue, will require rendering or decoding from cache
QFutureWatcher* watcher = new QFutureWatcher();
connect(watcher,
@@ -382,7 +418,7 @@ void ViewerWidget::UpdateTextureFromNode(const rational& time)
this,
&ViewerWidget::RendererGeneratedFrame);
- watcher->setFuture(renderer_->RenderFrame(time, true));
+ watcher->setFuture(GetFrame(time, true));
}
void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
@@ -485,11 +521,11 @@ void ViewerWidget::SetColorTransform(const ColorTransform &transform, ViewerDisp
void ViewerWidget::FillPlaybackQueue()
{
// FIXME: Replace with an asynchronous approach
- while (playback_queue_.size() < 8) {
+ while (playback_queue_.size() < 16) {
rational next_time = Timecode::timestamp_to_time(playback_queue_next_frame_,
timebase());
playback_queue_next_frame_ += playback_speed_;
- QFuture future = renderer_->RenderFrame(next_time, false);
+ QFuture future = GetFrame(next_time, false);
future.waitForFinished();
playback_queue_.AppendTimewise({future.result()->timestamp(), future.result()}, playback_speed_);
@@ -507,7 +543,7 @@ QString ViewerWidget::GetCachedFilenameFromTime(const rational &time)
if (!hash.isEmpty()) {
return GetConnectedNode()->video_frame_cache()->CachePathName(
hash,
- PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline));
+ GetCurrentPixelFormat());
}
}
@@ -519,45 +555,6 @@ bool ViewerWidget::FrameExistsAtTime(const rational &time)
return GetConnectedNode() && time < GetConnectedNode()->GetLength();
}
-FramePtr ViewerWidget::DecodeCachedImage(const QString &fn)
-{
- FramePtr frame = nullptr;
-
- if (!fn.isEmpty() && QFileInfo::exists(fn)) {
- auto input = OIIO::ImageInput::open(fn.toStdString());
-
- if (input) {
-
- PixelFormat::Format image_format = PixelFormat::OIIOFormatToOliveFormat(input->spec().format,
- input->spec().nchannels == kRGBAChannels);
-
- frame = Frame::Create();
-
- frame->set_video_params(VideoRenderingParams(input->spec().width,
- input->spec().height,
- image_format));
-
- frame->allocate();
-
- input->read_image(input->spec().format,
- frame->data(),
- OIIO::AutoStride,
- frame->linesize_bytes());
-
- input->close();
-
-#if OIIO_VERSION < 10903
- OIIO::ImageInput::destroy(input);
-#endif
-
- } else {
- qWarning() << "OIIO Error:" << OIIO::geterror().c_str();
- }
- }
-
- return frame;
-}
-
void ViewerWidget::SetDisplayImage(FramePtr frame, bool main_only)
{
display_widget_->SetImage(frame);
@@ -587,7 +584,27 @@ void ViewerWidget::RequestNextFrameForQueue()
&QFutureWatcher::finished,
this,
&ViewerWidget::RendererGeneratedFrameForQueue);
- watcher->setFuture(renderer_->RenderFrame(next_time, false));
+ watcher->setFuture(GetFrame(next_time, false));
+}
+
+PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const
+{
+ return PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOffline);
+}
+
+QFuture ViewerWidget::GetFrame(const rational &t, bool clear_render_queue)
+{
+ QByteArray cached_hash = GetConnectedNode()->video_frame_cache()->GetHash(t);
+ if (cached_hash.isEmpty()) {
+ // Frame hasn't been cached, start render job
+ return renderer_->RenderFrame(t, clear_render_queue);
+ } else {
+ // Frame has been cached, grab the frame
+ QString cache_fn = GetConnectedNode()->video_frame_cache()->CachePathName(cached_hash,
+ GetCurrentPixelFormat());
+
+ return QtConcurrent::run(DecodeCachedImage, cache_fn, t);
+ }
}
void ViewerWidget::UpdateStack()
@@ -699,7 +716,7 @@ void ViewerWidget::UpdateRendererParameters()
renderer_->SetDivider(divider_);
renderer_->SetMode(render_mode);
- renderer_->SetPixelFormat(PixelFormat::instance()->GetConfiguredFormatForMode(render_mode));
+ renderer_->SetPixelFormat(GetCurrentPixelFormat());
display_widget_->SetVideoParams(GetConnectedNode()->video_params());
@@ -859,11 +876,7 @@ void ViewerWidget::Pause()
window->Pause();
}
- {
- playback_queue_.lock()->lock();
- playback_queue_.clear();
- playback_queue_.lock()->unlock();
- }
+ playback_queue_.clear();
}
}
diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h
index 66d5364ea..9958c1ef8 100644
--- a/app/widget/viewer/viewer.h
+++ b/app/widget/viewer/viewer.h
@@ -182,12 +182,14 @@ private:
bool FrameExistsAtTime(const rational& time);
- FramePtr DecodeCachedImage(const QString& fn);
-
void SetDisplayImage(FramePtr frame, bool main_only);
void RequestNextFrameForQueue();
+ PixelFormat::Format GetCurrentPixelFormat() const;
+
+ QFuture GetFrame(const rational& t, bool clear_render_queue);
+
QStackedWidget* stack_;
ViewerSizer* sizer_;
@@ -240,7 +242,7 @@ private slots:
void SetZoomFromMenu(QAction* action);
- void ViewerInvalidatedRange(const TimeRange &range);
+ void ViewerInvalidatedRange(const OLIVE_NAMESPACE::TimeRange &range);
void UpdateStack();
diff --git a/app/widget/viewer/viewerqueue.h b/app/widget/viewer/viewerqueue.h
index 3cb6443d9..6c3704867 100644
--- a/app/widget/viewer/viewerqueue.h
+++ b/app/widget/viewer/viewerqueue.h
@@ -56,14 +56,6 @@ public:
}
}
- QMutex* lock()
- {
- return &queue_lock_;
- }
-
-private:
- QMutex queue_lock_;
-
};
OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/viewer/viewerwindow.cpp b/app/widget/viewer/viewerwindow.cpp
index 04ae55afb..3325be924 100644
--- a/app/widget/viewer/viewerwindow.cpp
+++ b/app/widget/viewer/viewerwindow.cpp
@@ -77,7 +77,6 @@ void ViewerWindow::Pause()
{
disconnect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWindow::UpdateFromQueue);
- QMutexLocker locker(queue_.lock());
queue_.clear();
}
@@ -103,7 +102,6 @@ void ViewerWindow::UpdateFromQueue()
rational time = Timecode::timestamp_to_time(t, playback_timebase_);
- QMutexLocker locker(queue_.lock());
while (!queue_.isEmpty()) {
const ViewerPlaybackFrame& pf = queue_.first();
diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp
index 4b9791b2b..92345a34f 100644
--- a/app/window/mainwindow/mainmenu.cpp
+++ b/app/window/mainwindow/mainmenu.cpp
@@ -27,7 +27,9 @@
#include "config/config.h"
#include "core.h"
#include "dialog/actionsearch/actionsearch.h"
+#include "dialog/task/task.h"
#include "panel/panelmanager.h"
+#include "task/cache/cache.h"
#include "tool/tool.h"
#include "ui/style/style.h"
#include "undo/undostack.h"
@@ -611,7 +613,16 @@ void MainMenu::OpenRecentItemTriggered()
void MainMenu::SequenceCacheTriggered()
{
- qDebug() << "STUB";
+ TimeBasedPanel* p = PanelManager::instance()->MostRecentlyFocused();
+
+ if (p && p->GetConnectedViewer()) {
+ // FIXME: Hardcoded divider...
+ // FIXME: Consider preventing caching the footage viewer
+ CacheTask* task = new CacheTask(p->GetConnectedViewer(), 2, false);
+
+ TaskDialog* dialog = new TaskDialog(task, tr("Caching Sequence"), parentWidget());
+ dialog->open();
+ }
}
void MainMenu::SequenceCacheInOutTriggered()