implemented core UI caching feedback functionality

Not a perfect implementation yet, but this shows UI feedback on what frames are
cached and which ones aren't.
This commit is contained in:
itsmattkc
2020-01-03 05:38:20 +11:00
parent f13f5b5fdb
commit db146b376a
18 changed files with 232 additions and 99 deletions
+45
View File
@@ -58,6 +58,11 @@ TimeRange TimeRange::CombineWith(const TimeRange &a) const
return Combine(a, *this);
}
bool TimeRange::Contains(const TimeRange &a) const
{
return (a.in() >= in() && a.out() <= out());
}
bool TimeRange::Overlap(const TimeRange &a, const TimeRange &b)
{
return !(a.out() < b.in() || a.in() > b.out());
@@ -85,3 +90,43 @@ uint qHash(const TimeRange &r, uint seed)
{
return qHash(r.in(), seed) ^ qHash(r.out(), seed);
}
void TimeRangeList::InsertTimeRange(const TimeRange &range)
{
for (int i=0;i<size();i++) {
const TimeRange& compare = at(i);
if (TimeRange::Overlap(range, compare)) {
replace(i, TimeRange::Combine(range, compare));
return;
}
}
append(range);
}
void TimeRangeList::RemoveTimeRange(const TimeRange &range)
{
for (int i=0;i<size();i++) {
const TimeRange& compare = at(i);
if (range.Contains(compare)) {
// This element is entirely encompassed in this range, remove it
removeAt(i);
i--;
} else if (compare.Contains(range)) {
// The remove range is within this element, only choice is to split the element into two
TimeRange first(compare.in(), range.in());
TimeRange last(range.out(), compare.out());
replace(i, first);
append(last);
} else if (compare.in() < range.in() && compare.out() > range.in()) {
// This element's out point overlaps the range's in, we'll trim it
(*this)[i].set_out(range.in());
} else if (compare.in() < range.out() && compare.out() > range.out()) {
// This element's in point overlaps the range's out, we'll trim it
(*this)[i].set_in(range.out());
}
}
}
+11
View File
@@ -20,6 +20,7 @@ public:
bool OverlapsWith(const TimeRange& a) const;
TimeRange CombineWith(const TimeRange& a) const;
bool Contains(const TimeRange& a) const;
static bool Overlap(const TimeRange& a, const TimeRange& b);
static TimeRange Combine(const TimeRange &a, const TimeRange &b);
@@ -34,4 +35,14 @@ private:
uint qHash(const TimeRange& r, uint seed);
class TimeRangeList : public QList<TimeRange> {
public:
TimeRangeList() = default;
void InsertTimeRange(const TimeRange& range);
void RemoveTimeRange(const TimeRange& range);
};
#endif // TIMERANGE_H
+1 -24
View File
@@ -39,10 +39,7 @@ void AudioRenderBackend::InvalidateCache(const rational &start_range, const rati
rational end_range_adj = qMin(GetSequenceLength(), end_range);
// Add the range to the list
cache_queue_.append(TimeRange(start_range_adj, end_range_adj));
// Remove any overlaps so we don't render the same thing twice
ValidateRanges();
cache_queue_.InsertTimeRange(TimeRange(start_range_adj, end_range_adj));
// Queue value update
QueueValueUpdate();
@@ -87,26 +84,6 @@ NodeInput *AudioRenderBackend::GetDependentInput()
return viewer_node()->samples_input();
}
void AudioRenderBackend::ValidateRanges()
{
for (int i=0;i<cache_queue_.size();i++) {
const TimeRange& range1 = cache_queue_.at(i);
for (int j=0;j<cache_queue_.size();j++) {
const TimeRange& range2 = cache_queue_.at(j);
if (TimeRange::Overlap(range1, range2) && i != j) {
// Combine with the first range
cache_queue_[i] = TimeRange::Combine(range1, range2);
// Remove the second range
cache_queue_.removeAt(j);
j--;
}
}
}
}
QString AudioRenderBackend::CachePathName()
{
QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id());
-2
View File
@@ -42,8 +42,6 @@ protected:
virtual bool CanRender() override;
private:
void ValidateRanges();
AudioRenderingParams params_;
};
+2 -2
View File
@@ -141,7 +141,7 @@ void OpenGLBackend::DecompileInternal()
shader_cache_.Clear();
}
void OpenGLBackend::EmitCachedFrameReady(const QList<rational> &times, const QVariant &value)
void OpenGLBackend::EmitCachedFrameReady(const QList<rational> &times, const QVariant &value, qint64 job_time)
{
OpenGLTextureCache::ReferencePtr ref = value.value<OpenGLTextureCache::ReferencePtr>();
OpenGLTexturePtr tex;
@@ -153,7 +153,7 @@ void OpenGLBackend::EmitCachedFrameReady(const QList<rational> &times, const QVa
}
foreach (const rational& t, times) {
emit CachedFrameReady(t, QVariant::fromValue(tex));
emit CachedFrameReady(t, QVariant::fromValue(tex), job_time);
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ protected:
virtual void DecompileInternal() override;
virtual void EmitCachedFrameReady(const QList<rational> &times, const QVariant& value) override;
virtual void EmitCachedFrameReady(const QList<rational> &times, const QVariant& value, qint64 job_time) override;
private:
OpenGLTexturePtr CopyTexture(OpenGLTexturePtr input);
+1 -1
View File
@@ -90,7 +90,7 @@ protected:
bool WorkerIsBusy(RenderWorker* worker) const;
void SetWorkerBusyState(RenderWorker* worker, bool busy);
QList<TimeRange> cache_queue_;
TimeRangeList cache_queue_;
QVector<RenderWorker*> processors_;
+18 -7
View File
@@ -159,6 +159,13 @@ void VideoRenderBackend::SetExportMode(bool enabled)
export_mode_ = enabled;
}
bool VideoRenderBackend::IsRendered(const rational &time) const
{
TimeRange range(time, time);
return !TimeIsQueued(range) && !render_job_info_.contains(range);
}
bool VideoRenderBackend::GenerateCacheIDInternal(QCryptographicHash& hash)
{
if (!params_.is_valid()) {
@@ -250,7 +257,7 @@ bool VideoRenderBackend::CanRender()
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) {
EmitCachedFrameReady({last_time_requested_}, value);
EmitCachedFrameReady({last_time_requested_}, value, job_time);
}
}
@@ -258,7 +265,13 @@ void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
SetFrameHash(dep, hash, job_time);
if (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();
@@ -269,9 +282,8 @@ void VideoRenderBackend::ThreadSkippedFrame(NodeDependency dep, qint64 job_time,
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
if (SetFrameHash(dep, hash, job_time)
&& last_time_requested_ == dep.in()
&& frame_cache_.HasHash(hash)) {
emit CachedTimeReady(dep.in());
emit CachedTimeReady(dep.in(), job_time);
}
// Queue up a new frame for this worker
@@ -282,9 +294,8 @@ void VideoRenderBackend::ThreadHashAlreadyExists(NodeDependency dep, qint64 job_
{
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
if (SetFrameHash(dep, hash, job_time)
&& dep.in() == last_time_requested_) {
emit CachedTimeReady(dep.in());
if (SetFrameHash(dep, hash, job_time)) {
emit CachedTimeReady(dep.in(), job_time);
}
// Queue up a new frame for this worker
+5 -3
View File
@@ -55,6 +55,8 @@ public:
void SetExportMode(bool enabled);
bool IsRendered(const rational& time) const;
public slots:
virtual void InvalidateCache(const rational &start_range, const rational &end_range) override;
@@ -97,13 +99,13 @@ protected:
virtual void ConnectWorkerToThis(RenderWorker* processor) override;
virtual void EmitCachedFrameReady(const QList<rational> &times, const QVariant& value) = 0;
virtual void EmitCachedFrameReady(const QList<rational> &times, const QVariant& value, qint64 job_time) = 0;
bool export_mode_;
signals:
void CachedFrameReady(const rational& time, QVariant value);
void CachedTimeReady(const rational& time);
void CachedFrameReady(const rational& time, QVariant value, qint64 job_time);
void CachedTimeReady(const rational& time, qint64 job_time);
private:
bool TimeIsQueued(const TimeRange &time) const;
@@ -81,6 +81,21 @@ void VideoRenderFrameCache::RemoveHashFromCurrentlyCaching(const QByteArray &has
currently_caching_lock_.unlock();
}
QList<rational> VideoRenderFrameCache::FramesWithHash(const QByteArray &hash)
{
QList<rational> times;
QMap<rational, QByteArray>::const_iterator iterator;
for (iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) {
if (iterator.value() == hash) {
times.append(iterator.key());
}
}
return times;
}
QString VideoRenderFrameCache::CachePathName(const QByteArray &hash) const
{
QDir this_cache_dir = QDir(GetMediaCacheLocation()).filePath(cache_id_);
@@ -41,6 +41,8 @@ public:
void RemoveHashFromCurrentlyCaching(const QByteArray& hash);
QList<rational> FramesWithHash(const QByteArray& hash);
private:
QMap<rational, QByteArray> time_hash_map_;
@@ -22,10 +22,10 @@ protected:
double scale_;
private:
rational timebase_;
double timebase_dbl_;
};
#endif // TIMELINESCALEDOBJECT_H
+1 -2
View File
@@ -32,7 +32,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
connect(timecode_label_, SIGNAL(ValueChanged(int64_t)), this, SLOT(UpdateInternalTime(const int64_t&)));
ruler_and_time_layout->addWidget(timecode_label_);
ruler_ = new TimeRuler(true);
ruler_ = new TimeRuler(true, true);
connect(ruler_, SIGNAL(TimeChanged(const int64_t&)), this, SIGNAL(TimeChanged(const int64_t&)));
connect(ruler_, SIGNAL(TimeChanged(const int64_t&)), this, SLOT(UpdateInternalTime(const int64_t&)));
ruler_and_time_layout->addWidget(ruler_);
@@ -60,7 +60,6 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
tools_.replace(::Tool::kRazor, std::make_shared<RazorTool>(this));
tools_.replace(::Tool::kSlip, std::make_shared<SlipTool>(this));
tools_.replace(::Tool::kSlide, std::make_shared<SlideTool>(this));
// tools_.replace(::Tool::kHand, std::make_shared<HandTool>(this));
tools_.replace(::Tool::kZoom, std::make_shared<ZoomTool>(this));
tools_.replace(::Tool::kTransition, std::make_shared<TransitionTool>(this));
//tools_.replace(::Tool::kRecord, new PointerTool(this)); FIXME: Implement
@@ -14,6 +14,7 @@ TimelineViewBase::TimelineViewBase(QWidget *parent) :
playhead_(0),
playhead_scene_left_(-1),
playhead_scene_right_(-1),
dragging_hand_(false),
limit_y_axis_(false)
{
setScene(&scene_);
+85 -28
View File
@@ -30,13 +30,14 @@
#include "config/config.h"
#include "core.h"
TimeRuler::TimeRuler(bool text_visible, QWidget* parent) :
TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* parent) :
QWidget(parent),
scroll_(0),
text_visible_(text_visible),
centered_text_(true),
scale_(1.0),
time_(0),
snapping_(false)
show_cache_status_(cache_status_visible)
{
QFontMetrics fm = fontMetrics();
@@ -44,6 +45,7 @@ TimeRuler::TimeRuler(bool text_visible, QWidget* parent) :
// Text height is used to calculate widget height
text_height_ = fm.height();
cache_status_height_ = text_height_ / 4;
// Get the "minimum" space allowed between two line markers on the ruler (in screen pixels)
// Mediocre but reliable way of scaling UI objects by font/DPI size
@@ -53,22 +55,7 @@ TimeRuler::TimeRuler(bool text_visible, QWidget* parent) :
playhead_width_ = minimum_gap_between_lines_;
// Text visibility affects height, so we set that here
SetTextVisible(text_visible);
}
void TimeRuler::SetTextVisible(bool e)
{
text_visible_ = e;
// Text visibility affects height, if text is visible the widget doubles in height with the top half for text and
// the bottom half for ruler markings
if (text_visible_) {
setMinimumHeight(text_height_ * 2);
} else {
setMinimumHeight(text_height_);
}
update();
UpdateHeight();
}
const double &TimeRuler::scale()
@@ -94,16 +81,20 @@ void TimeRuler::SetTimebase(const rational &r)
update();
}
void TimeRuler::SetSnapping(bool snapping)
{
snapping_ = snapping;
}
const int64_t &TimeRuler::GetTime()
{
return time_;
}
void TimeRuler::SetCacheStatusLength(const rational &length)
{
cache_length_ = length;
dirty_cache_ranges_.RemoveTimeRange(TimeRange(length, RATIONAL_MAX));
update();
}
void TimeRuler::SetTime(const int64_t &r)
{
time_ = r;
@@ -118,6 +109,20 @@ void TimeRuler::SetScroll(int s)
update();
}
void TimeRuler::CacheInvalidatedRange(const rational& in, const rational& out)
{
dirty_cache_ranges_.InsertTimeRange(TimeRange(in, out));
update();
}
void TimeRuler::CacheTimeReady(const rational &time)
{
dirty_cache_ranges_.RemoveTimeRange(TimeRange(time, time + timebase_));
update();
}
void TimeRuler::paintEvent(QPaintEvent *)
{
// Nothing to paint if the timebase is invalid
@@ -198,11 +203,16 @@ void TimeRuler::paintEvent(QPaintEvent *)
// Calculate line dimensions
QFontMetrics fm = p.fontMetrics();
int line_bottom = height();
if (show_cache_status_) {
line_bottom -= cache_status_height_;
}
int long_height = fm.height();
int short_height = long_height/2;
int long_y = height() - long_height;
int short_y = height() - short_height;
int line_bottom = height();
int long_y = line_bottom - long_height;
int short_y = line_bottom - short_height;
// Draw long lines
int last_long_unit = -1;
@@ -267,12 +277,34 @@ void TimeRuler::paintEvent(QPaintEvent *)
}
}
// If cache status is enabled
if (show_cache_status_) {
int cache_screen_length = qMin(TimeToScreen(cache_length_), width());
if (cache_screen_length > 0) {
int cache_y = height() - cache_status_height_;
p.fillRect(0, cache_y, cache_screen_length , cache_status_height_, Qt::green);
foreach (const TimeRange& range, dirty_cache_ranges_) {
int range_left = TimeToScreen(range.in());
int range_right = TimeToScreen(range.out());
if (range_left >= width() || range_right < 0) {
continue;
}
p.fillRect(qMax(0, range_left), cache_y, qMin(width(), range_right) - range_left, cache_status_height_, Qt::red);
}
}
}
// Draw the playhead if it's on screen at the moment
int playhead_pos = qFloor(static_cast<double>(time_) * scale_ * timebase_dbl_) - scroll_;
int playhead_pos = UnitToScreen(time_);
if (playhead_pos + playhead_width_ >= 0 && playhead_pos - playhead_width_ < width()) {
p.setPen(Qt::NoPen);
p.setBrush(style_.PlayheadColor());
DrawPlayhead(&p, playhead_pos, height());
DrawPlayhead(&p, playhead_pos, line_bottom);
}
}
@@ -317,6 +349,16 @@ int64_t TimeRuler::ScreenToUnit(int screen)
return qFloor(ScreenToUnitFloat(screen));
}
int TimeRuler::UnitToScreen(int64_t unit)
{
return qFloor(static_cast<double>(unit) * scale_ * timebase_dbl_) - scroll_;
}
int TimeRuler::TimeToScreen(const rational &time)
{
return qFloor(time.toDouble() * scale_) - scroll_;
}
void TimeRuler::SeekToScreenPoint(int screen)
{
int64_t timestamp = qMax(0, qRound(ScreenToUnitFloat(screen)));
@@ -325,3 +367,18 @@ void TimeRuler::SeekToScreenPoint(int screen)
emit TimeChanged(timestamp);
}
void TimeRuler::UpdateHeight()
{
int height = text_height_;
if (text_visible_) {
height += text_height_;
}
if (show_cache_status_) {
height += cache_status_height_;
}
setFixedHeight(height);
}
+20 -14
View File
@@ -25,15 +25,14 @@
#include <QWidget>
#include "common/rational.h"
#include "common/timerange.h"
#include "widget/timelinewidget/view/timelineplayhead.h"
class TimeRuler : public QWidget
{
Q_OBJECT
public:
TimeRuler(bool text_visible = true, QWidget* parent = nullptr);
void SetTextVisible(bool e);
TimeRuler(bool text_visible = true, bool cache_status_visible = false, QWidget* parent = nullptr);
const double& scale();
void SetScale(const double& d);
@@ -42,8 +41,6 @@ public:
void SetCenteredText(bool c);
void SetSnapping(bool snapping);
const int64_t& GetTime();
public slots:
@@ -51,6 +48,12 @@ public slots:
void SetScroll(int s);
void CacheInvalidatedRange(const rational& in, const rational& out);
void CacheTimeReady(const rational& time);
void SetCacheStatusLength(const rational& length);
protected:
virtual void paintEvent(QPaintEvent* e) override;
@@ -64,14 +67,7 @@ signals:
void TimeChanged(int64_t);
private:
enum Component {
kNone,
kDay,
kHour,
kMinute,
kSecond,
kFrame
};
void UpdateHeight();
void DrawPlayhead(QPainter* p, int x, int y);
@@ -79,10 +75,16 @@ private:
int64_t ScreenToUnit(int screen);
int UnitToScreen(int64_t unit);
int TimeToScreen(const rational& time);
void SeekToScreenPoint(int screen);
int text_height_;
int cache_status_height_;
int minimum_gap_between_lines_;
int playhead_width_;
@@ -105,7 +107,11 @@ private:
TimelinePlayhead style_;
bool snapping_;
bool show_cache_status_;
rational cache_length_;
TimeRangeList dirty_cache_ranges_;
};
+19 -12
View File
@@ -49,7 +49,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
sizer_->SetWidget(gl_widget_);
// Create time ruler
ruler_ = new TimeRuler(false);
ruler_ = new TimeRuler(false, true);
layout->addWidget(ruler_);
connect(ruler_, SIGNAL(TimeChanged(int64_t)), this, SLOT(RulerTimeChange(int64_t)));
@@ -79,8 +79,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
// Start background renderers
video_renderer_ = new OpenGLBackend(this);
connect(video_renderer_, SIGNAL(CachedFrameReady(const rational&, QVariant)), this, SLOT(RendererCachedFrame(const rational&, QVariant)));
connect(video_renderer_, SIGNAL(CachedTimeReady(const rational&)), this, SLOT(RendererCachedTime(const rational&)));
connect(video_renderer_, &VideoRenderBackend::CachedFrameReady, this, &ViewerWidget::RendererCachedFrame);
connect(video_renderer_, &VideoRenderBackend::CachedTimeReady, this, &ViewerWidget::RendererCachedTime);
connect(video_renderer_, &VideoRenderBackend::CachedTimeReady, ruler_, &TimeRuler::CacheTimeReady);
audio_renderer_ = new AudioBackend(this);
}
@@ -138,9 +139,11 @@ void ViewerWidget::ConnectViewerNode(ViewerOutput *node, ColorManager* color_man
if (viewer_node_ != nullptr) {
SetTimebase(0);
disconnect(viewer_node_, SIGNAL(TimebaseChanged(const rational&)), this, SLOT(SetTimebase(const rational&)));
disconnect(viewer_node_, SIGNAL(SizeChanged(int, int)), this, SLOT(SizeChangedSlot(int, int)));
disconnect(viewer_node_, SIGNAL(LengthChanged(const rational&)), this, SLOT(LengthChangedSlot(const rational&)));
disconnect(viewer_node_, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase);
disconnect(viewer_node_, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot);
disconnect(viewer_node_, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
disconnect(viewer_node_, &ViewerOutput::LengthChanged, ruler_, &TimeRuler::SetCacheStatusLength);
disconnect(viewer_node_, &ViewerOutput::VideoChangedBetween, ruler_, &TimeRuler::CacheInvalidatedRange);
// Effectively disables the viewer and clears the state
SizeChangedSlot(0, 0);
@@ -159,9 +162,11 @@ void ViewerWidget::ConnectViewerNode(ViewerOutput *node, ColorManager* color_man
if (viewer_node_ != nullptr) {
SetTimebase(viewer_node_->video_params().time_base());
connect(viewer_node_, SIGNAL(TimebaseChanged(const rational&)), this, SLOT(SetTimebase(const rational&)));
connect(viewer_node_, SIGNAL(SizeChanged(int, int)), this, SLOT(SizeChangedSlot(int, int)));
connect(viewer_node_, SIGNAL(LengthChanged(const rational&)), this, SLOT(LengthChangedSlot(const rational&)));
connect(viewer_node_, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase);
connect(viewer_node_, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot);
connect(viewer_node_, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
connect(viewer_node_, &ViewerOutput::LengthChanged, ruler_, &TimeRuler::SetCacheStatusLength);
connect(viewer_node_, &ViewerOutput::VideoChangedBetween, ruler_, &TimeRuler::CacheInvalidatedRange);
SizeChangedSlot(viewer_node_->video_params().width(), viewer_node_->video_params().height());
LengthChangedSlot(viewer_node_->Length());
@@ -407,16 +412,18 @@ void ViewerWidget::PlaybackTimerUpdate()
SetTime(current_time);
}
void ViewerWidget::RendererCachedFrame(const rational &time, QVariant value)
void ViewerWidget::RendererCachedFrame(const rational &time, QVariant value, qint64 job_time)
{
if (GetTime() == time) {
SetTexture(value.value<OpenGLTexturePtr>());
frame_cache_job_time_ = job_time;
}
}
void ViewerWidget::RendererCachedTime(const rational &time)
void ViewerWidget::RendererCachedTime(const rational &time, qint64 job_time)
{
if (GetTime() == time) {
if (GetTime() == time && job_time > frame_cache_job_time_) {
UpdateTextureFromNode(GetTime());
}
}
+4 -2
View File
@@ -171,13 +171,15 @@ private:
int playback_speed_;
qint64 frame_cache_job_time_;
private slots:
void RulerTimeChange(int64_t);
void PlaybackTimerUpdate();
void RendererCachedFrame(const rational& time, QVariant value);
void RendererCachedTime(const rational& time);
void RendererCachedFrame(const rational& time, QVariant value, qint64 job_time);
void RendererCachedTime(const rational& time, qint64 job_time);
void SizeChangedSlot(int width, int height);