Files
oak-editor/app/widget/viewer/viewerdisplay.cpp
T
itsmattkc ae3393f031 opengl: use shared contexts and downgrade to ES 2.0 (BIG CHANGE)
If you run into new OpenGL issues, this is likely the commit that caused it.
I've tested this on as many systems as I have access to (including AMD, Apple,
Intel, and NVIDIA GPUs with a mixture of Windows, Linux, and macOS) and it
appears to work fine, however we all know how unpredictable GPUs can be.
This should help increase GPU performance and compatibility.

Squashed commit of the following:

commit fa3c0ac7c469aebe2941a803736bdc06cdd5a2d8
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 13:17:44 2021 +1000

    viewer: updated pixel sampling for shared contexts

commit 9334261da235f09cb67b193c5bb6169abd7a95f7
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 13:12:39 2021 +1000

    pixelsampler: show color preview box

commit dacd9be6971c5c0312ae45c8db349d47910a80dd
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 12:43:32 2021 +1000

    renderer: interlace on GPU

commit 074cea06cb87e9fdc4e8ea6b09587be6df296057
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 12:43:14 2021 +1000

    renderer: added texture clear option

commit 5d4f86b4cf1b1dcd3fb8e8c854ffa7f6fb8ca346
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 12:42:12 2021 +1000

    renderer: updated scopes for shared textures

commit 6fad96358d189874cf45f7887b7e13cc2b1603c4
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 11:42:05 2021 +1000

    opengl: fixed edge case

commit 1a19ffb53703ac6f30ed210f6d8eb5fc4b7b0f2d
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 10:56:13 2021 +1000

    opengl: fix deinterlace shader

commit 085961aa39cca90ebf51b919a78ef07a4882f0e1
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 10:08:40 2021 +1000

    opengl: downgrade to ES 2.0

    With half-float/float/3D extensions, this should be doable

commit ea1ab45cbcf5e017dfd0d8f28b231ff3255dfe92
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Thu May 27 10:06:59 2021 +1000

    renderer: fixed mutex deadlock

commit ecc2f08b16f03421870c5b61625ecd26f2f96582
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 21:28:13 2021 +1000

    opengl: fixed shared contexts

commit 261c689ea8089463155cf11b9ab15aa3f879f439
Merge: bfd4338a 91c88b1c
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 19:52:00 2021 +1000

    Merge branch 'master' into shared-contexts

commit bfd4338aaa3b09b6fbb7717c637face8d5490721
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 18:52:38 2021 +1000

    viewer: remove debug line

commit 45868d8a396181bf81c24b92a1ac5ce9ffed3426
Merge: 642f8e9d d0bd5e21
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 17:21:48 2021 +1000

    Merge branch 'master' into shared-contexts

commit 642f8e9df7d7d1a9ed4202d428f4d968ff668552
Merge: 861e5b89 4de0a727
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 17:01:38 2021 +1000

    Merge branch 'master' of https://github.com/olive-editor/olive into shared-contexts

commit 861e5b89a38c281897dc44b20e3587d39c4f808c
Merge: 472598a7 ef31c3fb
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Wed May 26 14:28:21 2021 +1000

    Merge branch 'master' into shared-contexts

commit 472598a740a095aa43a4aad5680dd5abcf453a4c
Author: itsmattkc <itsmattkc@gmail.com>
Date:   Fri May 14 16:29:35 2021 +1000

    attempt at sharing contexts
2021-05-29 20:02:45 +10:00

603 lines
16 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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 "viewerdisplay.h"
#include <OpenImageIO/imagebuf.h>
#include <QFileInfo>
#include <QMessageBox>
#include <QMouseEvent>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
#include <QPainter>
#include "common/define.h"
#include "common/functiontimer.h"
#include "config/config.h"
#include "core.h"
#include "node/traverser.h"
namespace olive {
#define super ManagedDisplayWidget
ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) :
super(parent),
deinterlace_texture_(nullptr),
signal_cursor_color_(false),
gizmos_(nullptr),
gizmo_click_(false),
hand_dragging_(false),
deinterlace_(false),
show_fps_(false),
frames_skipped_(0),
show_widget_background_(false),
push_mode_(kPushNull)
{
connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::UpdateCursor);
connect(this, &ViewerDisplayWidget::InnerWidgetMouseMove, this, &ViewerDisplayWidget::EmitColorAtCursor);
// Initializes cursor based on tool
UpdateCursor();
const int kFrameRateAverageCount = 8;
frame_rate_averages_.resize(kFrameRateAverageCount);
}
void ViewerDisplayWidget::SetMatrixTranslate(const QMatrix4x4 &mat)
{
translate_matrix_ = mat;
UpdateMatrix();
}
void ViewerDisplayWidget::SetMatrixZoom(const QMatrix4x4 &mat)
{
scale_matrix_ = mat;
UpdateMatrix();
}
void ViewerDisplayWidget::SetMatrixCrop(const QMatrix4x4 &mat)
{
crop_matrix_ = mat;
update();
}
void ViewerDisplayWidget::UpdateCursor()
{
if (Core::instance()->tool() == Tool::kHand) {
setCursor(Qt::OpenHandCursor);
} else {
unsetCursor();
}
}
void ViewerDisplayWidget::SetSignalCursorColorEnabled(bool e)
{
signal_cursor_color_ = e;
inner_widget()->setMouseTracking(e);
}
void ViewerDisplayWidget::SetImage(const QVariant &buffer)
{
load_frame_ = buffer;
if (load_frame_.isNull()) {
push_mode_ = kPushNull;
} else {
push_mode_ = kPushFrame;
}
update();
}
void ViewerDisplayWidget::SetBlank()
{
push_mode_ = kPushBlank;
update();
}
void ViewerDisplayWidget::SetDeinterlacing(bool e)
{
deinterlace_ = e;
if (!deinterlace_) {
if (!deinterlace_shader_.isNull()) {
renderer()->DestroyNativeShader(deinterlace_shader_);
deinterlace_shader_.clear();
}
deinterlace_texture_ = nullptr;
}
update();
}
const ViewerSafeMarginInfo &ViewerDisplayWidget::GetSafeMargin() const
{
return safe_margin_;
}
void ViewerDisplayWidget::SetSafeMargins(const ViewerSafeMarginInfo &safe_margin)
{
if (safe_margin_ != safe_margin) {
safe_margin_ = safe_margin;
update();
}
}
void ViewerDisplayWidget::SetGizmos(Node *node)
{
if (gizmos_ != node) {
gizmos_ = node;
update();
}
}
void ViewerDisplayWidget::SetVideoParams(const VideoParams &params)
{
gizmo_params_ = params;
if (gizmos_) {
update();
}
}
void ViewerDisplayWidget::SetTime(const rational &time)
{
time_ = time;
if (gizmos_) {
update();
}
}
QPoint ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(QPoint pos)
{
/*
* Inversion will only fail if the viewer has been scaled by 0 in any direction
* which I think should never happen.
*/
return pos * GenerateGizmoTransform().inverted();
}
void ViewerDisplayWidget::ResetFPSTimer()
{
fps_timer_start_ = QDateTime::currentMSecsSinceEpoch();
fps_timer_update_count_ = 0;
frames_skipped_ = 0;
frame_rate_average_count_ = 0;
Core::instance()->ClearStatusBarMessage();
}
void ViewerDisplayWidget::IncrementSkippedFrames()
{
frames_skipped_++;
Core::instance()->ShowStatusBarMessage(tr("%n skipped frame(s) detected during playback", nullptr, frames_skipped_), 10000);
}
void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && gizmos_
&& gizmos_->GizmoPress(gizmo_db_, TransformViewerSpaceToBufferSpace(event->pos()))) {
// Handle gizmo click
gizmo_click_ = true;
gizmo_drag_time_ = GetGizmoTime();
gizmo_start_drag_ = event->pos();
} else if (IsHandDrag(event)) {
// Handle hand drag
hand_last_drag_pos_ = event->pos();
hand_dragging_ = true;
emit HandDragStarted();
setCursor(Qt::ClosedHandCursor);
} else {
if (event->button() == Qt::LeftButton) {
// Handle standard drag
emit DragStarted();
}
super::mousePressEvent(event);
}
}
void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event)
{
// Handle hand dragging
if (hand_dragging_) {
// Emit movement
emit HandDragMoved(event->x() - hand_last_drag_pos_.x(),
event->y() - hand_last_drag_pos_.y());
hand_last_drag_pos_ = event->pos();
} else if (gizmo_click_) {
// Signal movement
gizmos_->GizmoMove(TransformViewerSpaceToBufferSpace(event->pos()),
gizmo_drag_time_);
gizmo_start_drag_ = event->pos();
update();
} else {
// Default behavior
super::mouseMoveEvent(event);
}
}
void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (hand_dragging_) {
// Handle hand drag
emit HandDragEnded();
hand_dragging_ = false;
UpdateCursor();
} else if (gizmo_click_) {
// Handle gizmo
gizmos_->GizmoRelease();
gizmo_click_ = false;
} else {
// Default behavior
super::mouseReleaseEvent(event);
}
}
void ViewerDisplayWidget::dragEnterEvent(QDragEnterEvent *event)
{
emit DragEntered(event);
if (!event->isAccepted()) {
super::dragEnterEvent(event);
}
}
void ViewerDisplayWidget::dragLeaveEvent(QDragLeaveEvent *event)
{
emit DragLeft(event);
if (!event->isAccepted()) {
super::dragLeaveEvent(event);
}
}
void ViewerDisplayWidget::dropEvent(QDropEvent *event)
{
emit Dropped(event);
if (!event->isAccepted()) {
super::dropEvent(event);
}
}
void ViewerDisplayWidget::showEvent(QShowEvent *event)
{
emit VisibilityChanged(true);
super::showEvent(event);
}
void ViewerDisplayWidget::hideEvent(QHideEvent *event)
{
emit VisibilityChanged(false);
super::hideEvent(event);
}
void ViewerDisplayWidget::OnPaint()
{
// Clear background to empty
QColor bg_color = show_widget_background_ ? palette().window().color() : Qt::black;
renderer()->ClearDestination(nullptr, bg_color.redF(), bg_color.greenF(), bg_color.blueF());
// We only draw if we have a pipeline
if (push_mode_ != kPushNull) {
// Draw texture through color transform
int device_width = width() * devicePixelRatioF();
int device_height = height() * devicePixelRatioF();
VideoParams::Format device_format = static_cast<VideoParams::Format>(Config::Current()["OfflinePixelFormat"].toInt());
VideoParams device_params(device_width, device_height, device_format, VideoParams::kInternalChannelCount);
if (push_mode_ == kPushBlank) {
if (blank_shader_.isNull()) {
blank_shader_ = renderer()->CreateNativeShader(ShaderCode());
}
ShaderJob job;
job.InsertValue(QStringLiteral("ove_mvpmat"), NodeValue(NodeValue::kMatrix, combined_matrix_flipped_));
job.InsertValue(QStringLiteral("ove_cropmatrix"), NodeValue(NodeValue::kMatrix, crop_matrix_));
renderer()->Blit(blank_shader_, job, device_params, false);
} else if (color_service()) {
if (FramePtr frame = load_frame_.value<FramePtr>()) {
// This is a CPU frame, upload it now
if (!texture_
|| texture_->renderer() != renderer() // Some implementations don't like it if we upload to a texture created in another (albeit shared) context
|| texture_->width() != frame->width()
|| texture_->height() != frame->height()
|| texture_->format() != frame->format()
|| texture_->channel_count() != frame->channel_count()) {
texture_ = renderer()->CreateTexture(frame->video_params(), frame->data(), frame->linesize_pixels());
} else {
texture_->Upload(frame->data(), frame->linesize_pixels());
}
} else if (TexturePtr texture = load_frame_.value<TexturePtr>()) {
// This is a GPU texture, switch to it directly
texture_ = texture;
}
emit TextureChanged(texture_);
push_mode_ = kPushUnnecessary;
TexturePtr texture_to_draw = texture_;
if (deinterlace_) {
if (deinterlace_shader_.isNull()) {
deinterlace_shader_ = renderer()->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/deinterlace.frag"))));
}
if (!deinterlace_texture_
|| deinterlace_texture_->params() != texture_to_draw->params()) {
// (Re)create texture
deinterlace_texture_ = renderer()->CreateTexture(texture_to_draw->params());
}
ShaderJob job;
job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, QVector2D(texture_to_draw->width(), texture_to_draw->height())));
job.InsertValue(QStringLiteral("ove_maintex"), NodeValue(NodeValue::kTexture, QVariant::fromValue(texture_to_draw)));
renderer()->BlitToTexture(deinterlace_shader_, job, deinterlace_texture_.get());
texture_to_draw = deinterlace_texture_;
}
renderer()->BlitColorManaged(color_service(), texture_to_draw, true, device_params, false,
combined_matrix_flipped_, crop_matrix_);
}
}
// Draw gizmos if we have any
if (gizmos_) {
NodeTraverser gt;
gt.SetCacheVideoParams(gizmo_params_);
rational node_time = GetGizmoTime();
gizmo_db_ = gt.GenerateDatabase(gizmos_, QString(),
TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base()));
QPainter p(inner_widget());
p.setWorldTransform(GenerateGizmoTransform());
gizmos_->DrawGizmos(gizmo_db_, &p);
}
// Draw action/title safe areas
if (safe_margin_.is_enabled()) {
QPainter p(inner_widget());
p.setWorldTransform(GenerateWorldTransform());
p.setPen(Qt::lightGray);
p.setBrush(Qt::NoBrush);
int x = 0, y = 0, w = width(), h = height();
if (safe_margin_.custom_ratio()) {
double widget_ar = static_cast<double>(width()) / static_cast<double>(height());
if (widget_ar > safe_margin_.ratio()) {
// Widget is wider than margins
w = h * safe_margin_.ratio();
x = width() / 2 - w / 2;
} else {
h = w / safe_margin_.ratio();
y = height() / 2 - h / 2;
}
}
p.drawRect(w / 20 + x, h / 20 + y, w / 10 * 9, h / 10 * 9);
p.drawRect(w / 10 + x, h / 10 + y, w / 10 * 8, h / 10 * 8);
int cross = qMin(w, h) / 32;
QLine lines[] = {QLine(rect().center().x() - cross, rect().center().y(),rect().center().x() + cross, rect().center().y()),
QLine(rect().center().x(), rect().center().y() - cross, rect().center().x(), rect().center().y() + cross)};
p.drawLines(lines, 2);
}
if (show_fps_) {
{
qint64 now = QDateTime::currentMSecsSinceEpoch();
double frame_rate;
if (now == fps_timer_start_) {
// This will cause a divide by zero, so we do nothing here
frame_rate = 0;
} else {
frame_rate = double(fps_timer_update_count_) / double((now - fps_timer_start_)/1000.0);
}
if (frame_rate > 0) {
frame_rate_averages_[frame_rate_average_count_%frame_rate_averages_.size()] = frame_rate;
frame_rate_average_count_++;
}
}
if (frame_rate_average_count_ >= frame_rate_averages_.size()) {
QPainter p(inner_widget());
double average = 0.0;
for (int i=0; i<frame_rate_averages_.size(); i++) {
average += frame_rate_averages_[i];
}
average /= double(frame_rate_averages_.size());
DrawTextWithCrudeShadow(&p, inner_widget()->rect(), tr("%1 FPS").arg(QString::number(average, 'f', 1)));
if (frames_skipped_ > 0) {
DrawTextWithCrudeShadow(&p, inner_widget()->rect().adjusted(0, p.fontMetrics().height(), 0, 0),
tr("%1 frames skipped").arg(frames_skipped_));
}
}
}
}
void ViewerDisplayWidget::OnDestroy()
{
renderer()->DestroyNativeShader(deinterlace_shader_);
deinterlace_shader_.clear();
renderer()->DestroyNativeShader(blank_shader_);
blank_shader_.clear();
super::OnDestroy();
texture_ = nullptr;
deinterlace_texture_ = nullptr;
if (load_frame_.isNull()) {
push_mode_ = kPushNull;
} else {
push_mode_ = kPushFrame;
}
}
QPointF ViewerDisplayWidget::GetTexturePosition(const QPoint &screen_pos)
{
return GetTexturePosition(screen_pos.x(), screen_pos.y());
}
QPointF ViewerDisplayWidget::GetTexturePosition(const QSize &size)
{
return GetTexturePosition(size.width(), size.height());
}
QPointF ViewerDisplayWidget::GetTexturePosition(const double &x, const double &y)
{
return QPointF(x / gizmo_params_.width(),
y / gizmo_params_.height());
}
void ViewerDisplayWidget::DrawTextWithCrudeShadow(QPainter *painter, const QRect &rect, const QString &text)
{
painter->setPen(Qt::black);
painter->drawText(rect.adjusted(1, 1, 0, 0), text);
painter->setPen(Qt::white);
painter->drawText(rect, text);
}
rational ViewerDisplayWidget::GetGizmoTime()
{
return GetAdjustedTime(GetTimeTarget(), gizmos_, time_, true);
}
bool ViewerDisplayWidget::IsHandDrag(QMouseEvent *event) const
{
return event->button() == Qt::MiddleButton || Core::instance()->tool() == Tool::kHand;
}
void ViewerDisplayWidget::UpdateMatrix()
{
combined_matrix_ = scale_matrix_ * translate_matrix_;
combined_matrix_flipped_.setToIdentity();
combined_matrix_flipped_.scale(1.0, -1.0, 1.0);
combined_matrix_flipped_ *= combined_matrix_;
update();
}
QTransform ViewerDisplayWidget::GenerateWorldTransform()
{
/*
* Get matrix elements (roughly) as below in column major order
*
* | Sx 0 0 Tx |
* | 0 Sy 0 Ty |
* | 0 0 Sz Tz |
* | 0 0 0 1 |
*/
float *d = combined_matrix_.data();
QTransform world;
// Move corner of canvas to correct point
world.translate(width() * 0.5 - width() * *(d)*0.5, height() * 0.5 - height() * *(d + 5) * 0.5);
// Scale
world.scale(*(d), *(d + 5));
// Translate for mouse movement
world.translate(*(d + 12) * width() * 0.5 / *(d), *(d + 13) * height() * 0.5 / *(d + 5));
return world;
}
QTransform ViewerDisplayWidget::GenerateGizmoTransform()
{
QVector2D viewer_scale(GetTexturePosition(size()));
QTransform gizmo_transform = GenerateWorldTransform();
gizmo_transform.scale(viewer_scale.x(), viewer_scale.y());
gizmo_transform.scale(gizmo_params_.pixel_aspect_ratio().flipped().toDouble(), 1);
return gizmo_transform;
}
void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e)
{
// Do this no matter what, emits signal to any pixel samplers
if (signal_cursor_color_) {
Color reference, display;
if (texture_) {
QPointF pixel_pos = GenerateGizmoTransform().inverted().map(e->pos());
pixel_pos /= texture_->params().divider();
reference = renderer()->GetPixelFromTexture(texture_.get(), pixel_pos);
display = color_service()->ConvertColor(reference);
}
emit CursorColor(reference, display);
}
}
void ViewerDisplayWidget::SetShowFPS(bool e)
{
show_fps_ = e;
update();
}
}