cache: added cache task that can be manually invoked

This commit is contained in:
itsmattkc
2020-05-15 18:36:02 +10:00
parent 7c2280944b
commit 53a2956350
13 changed files with 346 additions and 76 deletions
+2
View File
@@ -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();
+7 -2
View File
@@ -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())));
+1
View File
@@ -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);
+4 -4
View File
@@ -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);
+1
View File
@@ -14,6 +14,7 @@
# 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(cache)
add_subdirectory(conform)
add_subdirectory(proxy)
+22
View File
@@ -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 <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
task/cache/cache.h
task/cache/cache.cpp
PARENT_SCOPE
)
+174
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "cache.h"
#include <QLinkedList>
#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<QByteArray> hash_future;
};
struct HashFrameFuturePair {
QByteArray hash;
QFuture<FramePtr> frame_future;
};
struct HashDownloadFuturePair {
QByteArray hash;
QFuture<void> 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<Sequence*>(viewer_->parent());
if (s->workarea()->enabled()) {
range_to_cache = range_to_cache.Intersects(s->workarea()->range());
}
}
// Get hashes for each frame
QLinkedList<TimeHashFuturePair> 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<rational> > times_to_render;
foreach (const TimeHashFuturePair& i, hash_list) {
times_to_render[i.hash_future.result()].append(i.time);
}
// Render all frames necessary
QLinkedList<HashFrameFuturePair> render_lookup_table;
{
QMap< QByteArray, QLinkedList<rational> >::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<HashDownloadFuturePair> download_futures;
// Iterators
QLinkedList<HashFrameFuturePair>::iterator i;
QLinkedList<HashDownloadFuturePair>::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<rational>& 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<double>(counter) / static_cast<double>(render_lookup_table.size())));
j = download_futures.erase(j);
} else {
j++;
}
}
}
}
}
OLIVE_NAMESPACE_EXIT
+49
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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
+69 -56
View File
@@ -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<FramePtr>* watcher = new QFutureWatcher<FramePtr>();
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<FramePtr> future = renderer_->RenderFrame(next_time, false);
QFuture<FramePtr> 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<FramePtr>::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<FramePtr> 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();
}
}
+5 -3
View File
@@ -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<FramePtr> 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();
-8
View File
@@ -56,14 +56,6 @@ public:
}
}
QMutex* lock()
{
return &queue_lock_;
}
private:
QMutex queue_lock_;
};
OLIVE_NAMESPACE_EXIT
-2
View File
@@ -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();
+12 -1
View File
@@ -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<TimeBasedPanel>();
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()