Files
oak-editor/app/widget/viewer/viewer.cpp
T
itsmattkc 0469fac4a9 node: send invalidatevisible signal through graph
When dragging a value in the UI, we use a "single frame update" because we want
to prioritize the currently visible frame to give visual feedback as soon as
possible. Previously, we generated a single frame InvalidateCache() signal
from the widgets themselves, but this had the major downside of not necessarily
emitting the time that the viewer was actually showing (due to either node time
transformations or times differing between the effects panels and the viewer
panels). Now, we send a different signal that viewers can handle themselves to
update the time that they're currently showing. This means the fast updating
will work no matter how many viewers are connected at whatever time each viewer
is set to.
2020-01-26 14:16:22 +11:00

565 lines
15 KiB
C++

/***
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 "viewer.h"
#include <QDateTime>
#include <QLabel>
#include <QResizeEvent>
#include <QtMath>
#include <QVBoxLayout>
#include "audio/audiomanager.h"
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "project/item/sequence/sequence.h"
#include "project/project.h"
#include "render/pixelservice.h"
#include "widget/menu/menu.h"
ViewerWidget::ViewerWidget(QWidget *parent) :
QWidget(parent),
viewer_node_(nullptr),
playback_speed_(0),
color_menu_enabled_(true),
divider_(Config::Current()["DefaultViewerDivider"].toInt())
{
// Set up main layout
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setMargin(0);
// Create main OpenGL-based view
sizer_ = new ViewerSizer();
layout->addWidget(sizer_);
gl_widget_ = new ViewerGLWidget();
connect(gl_widget_, &ViewerGLWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
sizer_->SetWidget(gl_widget_);
// Create time ruler
ruler_ = new TimeRuler(false, true);
layout->addWidget(ruler_);
connect(ruler_, &TimeRuler::TimeChanged, this, &ViewerWidget::RulerTimeChange);
// Create scrollbar
scrollbar_ = new QScrollBar(Qt::Horizontal);
layout->addWidget(scrollbar_);
connect(scrollbar_, &QScrollBar::valueChanged, ruler_, &TimeRuler::SetScroll);
scrollbar_->setPageStep(ruler_->width());
// Create lower controls
controls_ = new PlaybackControls();
controls_->SetTimecodeEnabled(true);
controls_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
connect(controls_, &PlaybackControls::PlayClicked, this, &ViewerWidget::Play);
connect(controls_, &PlaybackControls::PauseClicked, this, &ViewerWidget::Pause);
connect(controls_, &PlaybackControls::PrevFrameClicked, this, &ViewerWidget::PrevFrame);
connect(controls_, &PlaybackControls::NextFrameClicked, this, &ViewerWidget::NextFrame);
connect(controls_, &PlaybackControls::BeginClicked, this, &ViewerWidget::GoToStart);
connect(controls_, &PlaybackControls::EndClicked, this, &ViewerWidget::GoToEnd);
layout->addWidget(controls_);
// Connect timer
connect(&playback_timer_, &QTimer::timeout, this, &ViewerWidget::PlaybackTimerUpdate);
// FIXME: Magic number
ruler_->SetScale(48.0);
// Start background renderers
video_renderer_ = new OpenGLBackend(this);
connect(video_renderer_, &VideoRenderBackend::CachedFrameReady, this, &ViewerWidget::RendererCachedFrame);
connect(video_renderer_, &VideoRenderBackend::CachedTimeReady, this, &ViewerWidget::RendererCachedTime);
connect(video_renderer_, &VideoRenderBackend::CachedTimeReady, ruler_, &TimeRuler::CacheTimeReady);
connect(video_renderer_, &VideoRenderBackend::RangeInvalidated, ruler_, &TimeRuler::CacheInvalidatedRange);
audio_renderer_ = new AudioBackend(this);
connect(PixelService::instance(), &PixelService::FormatChanged, this, &ViewerWidget::UpdateRendererParameters);
}
void ViewerWidget::SetTimebase(const rational &r)
{
time_base_ = r;
time_base_dbl_ = r.toDouble();
ruler_->SetTimebase(r);
controls_->SetTimebase(r);
controls_->SetTime(ruler_->GetTime());
LengthChangedSlot(viewer_node_ ? viewer_node_->Length() : 0);
playback_timer_.setInterval(qFloor(r.toDouble()));
}
const double &ViewerWidget::scale() const
{
return ruler_->scale();
}
rational ViewerWidget::GetTime() const
{
return Timecode::timestamp_to_time(ruler_->GetTime(), time_base_);
}
void ViewerWidget::SetScale(const double &scale_)
{
ruler_->SetScale(scale_);
}
void ViewerWidget::SetTime(const int64_t &time)
{
ruler_->SetTime(time);
UpdateTimeInternal(time);
}
void ViewerWidget::TogglePlayPause()
{
if (IsPlaying()) {
Pause();
} else {
Play();
}
}
bool ViewerWidget::IsPlaying() const
{
return playback_timer_.isActive();
}
void ViewerWidget::ConnectViewerNode(ViewerOutput *node, ColorManager* color_manager)
{
if (viewer_node_ != nullptr) {
SetTimebase(0);
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::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible);
// Effectively disables the viewer and clears the state
SizeChangedSlot(0, 0);
gl_widget_->DisconnectColorManager();
}
viewer_node_ = node;
video_renderer_->SetViewerNode(viewer_node_);
audio_renderer_->SetViewerNode(viewer_node_);
// Set texture to new texture (or null if no viewer node is available)
UpdateTextureFromNode(GetTime());
if (viewer_node_ != nullptr) {
SetTimebase(viewer_node_->video_params().time_base());
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::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible);
SizeChangedSlot(viewer_node_->video_params().width(), viewer_node_->video_params().height());
LengthChangedSlot(viewer_node_->Length());
if (color_manager) {
gl_widget_->ConnectColorManager(color_manager);
} else if (viewer_node_->parent()) {
gl_widget_->ConnectColorManager(static_cast<Sequence*>(viewer_node_->parent())->project()->color_manager());
} else {
qWarning() << "Failed to find a suitable color manager for the connected viewer node";
}
UpdateRendererParameters();
}
}
ViewerOutput *ViewerWidget::GetConnectedViewer() const
{
return viewer_node_;
}
void ViewerWidget::SetColorMenuEnabled(bool enabled)
{
color_menu_enabled_ = enabled;
}
void ViewerWidget::SetOverrideSize(int width, int height)
{
SizeChangedSlot(width, height);
}
void ViewerWidget::SetMatrix(const QMatrix4x4 &mat)
{
gl_widget_->SetMatrix(mat);
}
VideoRenderBackend *ViewerWidget::video_renderer() const
{
return video_renderer_;
}
void ViewerWidget::SetTexture(OpenGLTexturePtr tex)
{
gl_widget_->SetTexture(tex);
}
void ViewerWidget::UpdateTimeInternal(int64_t i)
{
rational time_set = rational(i) * time_base_;
controls_->SetTime(i);
if (viewer_node_ != nullptr && last_time_ != i) {
UpdateTextureFromNode(time_set);
PushScrubbedAudio();
}
last_time_ = i;
emit TimeChanged(i);
}
void ViewerWidget::UpdateTextureFromNode(const rational& time)
{
if (viewer_node_ == nullptr) {
SetTexture(nullptr);
} else {
SetTexture(video_renderer_->GetCachedFrameAsTexture(time));
}
}
void ViewerWidget::PlayInternal(int speed)
{
Q_ASSERT(speed != 0);
if (time_base_.isNull()) {
qWarning() << "ViewerWidget can't play with an invalid timebase";
return;
}
playback_speed_ = speed;
QIODevice* audio_src = audio_renderer_->GetAudioPullDevice();
if (audio_src != nullptr && audio_src->open(QIODevice::ReadOnly)) {
audio_src->seek(audio_renderer_->params().time_to_bytes(GetTime()));
AudioManager::instance()->SetOutputParams(audio_renderer_->params());
AudioManager::instance()->StartOutput(audio_src, playback_speed_);
}
start_msec_ = QDateTime::currentMSecsSinceEpoch();
start_timestamp_ = ruler_->GetTime();
playback_timer_.start();
controls_->ShowPauseButton();
}
void ViewerWidget::PushScrubbedAudio()
{
if (Config::Current()["AudioScrubbing"].toBool() && !IsPlaying()) {
// Get audio src device from renderer
QIODevice* audio_src = audio_renderer_->GetAudioPullDevice();
if (audio_src != nullptr && audio_src->open(QFile::ReadOnly)) {
// Try to get one "frame" of audio
int size_of_sample = audio_renderer_->params().time_to_bytes(time_base_);
// Push audio
audio_src->seek(audio_renderer_->params().time_to_bytes(GetTime()));
QByteArray frame_audio = audio_src->read(size_of_sample);
AudioManager::instance()->SetOutputParams(audio_renderer_->params());
AudioManager::instance()->PushToOutput(frame_audio);
audio_src->close();
}
}
}
void ViewerWidget::UpdateRendererParameters()
{
if (!viewer_node_) {
return;
}
RenderMode::Mode render_mode = RenderMode::kOffline;
video_renderer_->SetParameters(VideoRenderingParams(viewer_node_->video_params(),
PixelService::instance()->GetConfiguredFormatForMode(render_mode),
render_mode,
divider_));
audio_renderer_->SetParameters(AudioRenderingParams(viewer_node_->audio_params(),
SampleFormat::GetConfiguredFormatForMode(render_mode)));
video_renderer_->InvalidateCache(0, viewer_node_->Length());
}
void ViewerWidget::ShowContextMenu(const QPoint &pos)
{
QMenu menu;
// Color options
if (gl_widget_->color_manager() && color_menu_enabled_) {
QStringList displays = gl_widget_->color_manager()->ListAvailableDisplays();
QMenu* ocio_display_menu = menu.addMenu(tr("Display"));
connect(ocio_display_menu, &QMenu::triggered, this, &ViewerWidget::ColorDisplayChanged);
foreach (const QString& d, displays) {
QAction* action = ocio_display_menu->addAction(d);
action->setCheckable(true);
action->setChecked(gl_widget_->ocio_display() == d);
action->setData(d);
}
QStringList views = gl_widget_->color_manager()->ListAvailableViews(gl_widget_->ocio_display());
QMenu* ocio_view_menu = menu.addMenu(tr("View"));
connect(ocio_view_menu, &QMenu::triggered, this, &ViewerWidget::ColorViewChanged);
foreach (const QString& v, views) {
QAction* action = ocio_view_menu->addAction(v);
action->setCheckable(true);
action->setChecked(gl_widget_->ocio_view() == v);
action->setData(v);
}
QStringList looks = gl_widget_->color_manager()->ListAvailableLooks();
QMenu* ocio_look_menu = menu.addMenu(tr("Look"));
connect(ocio_look_menu, &QMenu::triggered, this, &ViewerWidget::ColorLookChanged);
QAction* no_look_action = ocio_look_menu->addAction(tr("(None)"));
no_look_action->setCheckable(true);
no_look_action->setChecked(gl_widget_->ocio_look().isEmpty());
foreach (const QString& l, looks) {
QAction* action = ocio_look_menu->addAction(l);
action->setCheckable(true);
action->setChecked(gl_widget_->ocio_look() == l);
action->setData(l);
}
menu.addSeparator();
}
// Playback resolution
QMenu* playback_resolution_menu = menu.addMenu(tr("Resolution"));
playback_resolution_menu->addAction(tr("Full"))->setData(1);
playback_resolution_menu->addAction(tr("1/2"))->setData(2);
playback_resolution_menu->addAction(tr("1/4"))->setData(4);
playback_resolution_menu->addAction(tr("1/8"))->setData(8);
playback_resolution_menu->addAction(tr("1/16"))->setData(16);
connect(playback_resolution_menu, &QMenu::triggered, this, &ViewerWidget::SetDividerFromMenu);
foreach (QAction* a, playback_resolution_menu->actions()) {
a->setCheckable(true);
if (a->data() == divider_) {
a->setChecked(true);
}
}
menu.exec(mapToGlobal(pos));
}
void ViewerWidget::RulerTimeChange(int64_t i)
{
Pause();
UpdateTimeInternal(i);
}
void ViewerWidget::Play()
{
PlayInternal(1);
}
void ViewerWidget::Pause()
{
if (IsPlaying()) {
AudioManager::instance()->StopOutput();
playback_speed_ = 0;
controls_->ShowPlayButton();
playback_timer_.stop();
}
}
void ViewerWidget::GoToStart()
{
Pause();
SetTime(0);
}
void ViewerWidget::PrevFrame()
{
Pause();
SetTime(qMax(static_cast<int64_t>(0), ruler_->GetTime() - 1));
}
void ViewerWidget::NextFrame()
{
Pause();
SetTime(ruler_->GetTime() + 1);
}
void ViewerWidget::GoToEnd()
{
if (viewer_node_ != nullptr) {
Pause();
SetTime(Timecode::time_to_timestamp(viewer_node_->Length(), time_base_));
}
}
void ViewerWidget::ShuttleLeft()
{
int current_speed = playback_speed_;
if (current_speed != 0) {
Pause();
}
current_speed--;
if (current_speed != 0) {
PlayInternal(current_speed);
}
}
void ViewerWidget::ShuttleStop()
{
Pause();
}
void ViewerWidget::ShuttleRight()
{
int current_speed = playback_speed_;
if (current_speed != 0) {
Pause();
}
current_speed++;
if (current_speed != 0) {
PlayInternal(current_speed);
}
}
void ViewerWidget::SetOCIOParameters(const QString &display, const QString &view, const QString &look)
{
gl_widget_->SetOCIOParameters(display, view, look);
}
void ViewerWidget::SetOCIODisplay(const QString &display)
{
gl_widget_->SetOCIODisplay(display);
}
void ViewerWidget::SetOCIOView(const QString &view)
{
gl_widget_->SetOCIOView(view);
}
void ViewerWidget::SetOCIOLook(const QString &look)
{
gl_widget_->SetOCIOLook(look);
}
void ViewerWidget::PlaybackTimerUpdate()
{
int64_t real_time = QDateTime::currentMSecsSinceEpoch() - start_msec_;
int64_t frames_since_start = qRound(static_cast<double>(real_time) / (time_base_dbl_ * 1000));
int64_t current_time = start_timestamp_ + frames_since_start * playback_speed_;
if (current_time < 0) {
current_time = 0;
Pause();
}
SetTime(current_time);
}
void ViewerWidget::RendererCachedFrame(const rational &time, QVariant value, qint64 job_time)
{
if (GetTime() == time) {
frame_cache_job_time_ = job_time;
SetTexture(value.value<OpenGLTexturePtr>());
}
}
void ViewerWidget::RendererCachedTime(const rational &time, qint64 job_time)
{
if (GetTime() == time && job_time > frame_cache_job_time_) {
frame_cache_job_time_ = job_time;
UpdateTextureFromNode(GetTime());
}
}
void ViewerWidget::SizeChangedSlot(int width, int height)
{
sizer_->SetChildSize(width, height);
}
void ViewerWidget::LengthChangedSlot(const rational &length)
{
controls_->SetEndTime(Timecode::time_to_timestamp(length, time_base_));
ruler_->SetCacheStatusLength(length);
}
void ViewerWidget::resizeEvent(QResizeEvent *event)
{
// Set scrollbar page step to the width
scrollbar_->setPageStep(event->size().width());
}
void ViewerWidget::ColorDisplayChanged(QAction* action)
{
SetOCIODisplay(action->data().toString());
}
void ViewerWidget::ColorViewChanged(QAction *action)
{
SetOCIOView(action->data().toString());
}
void ViewerWidget::ColorLookChanged(QAction *action)
{
SetOCIOLook(action->data().toString());
}
void ViewerWidget::SetDividerFromMenu(QAction *action)
{
int divider = action->data().toInt();
if (divider <= 0) {
qWarning() << "Tried to set invalid divider:" << divider;
return;
}
divider_ = divider;
UpdateRendererParameters();
}
void ViewerWidget::InvalidateVisible()
{
video_renderer_->InvalidateCache(GetTime(), GetTime());
}