diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 477203d9f..8e36a6059 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "config/config.h" @@ -834,28 +835,65 @@ void OpenGLRenderer::ClearDestinationInternal(double r, double g, double b, GLuint OpenGLRenderer::CompileShader(GLenum type, const QString &code) { - static const QString shader_preamble = + const bool is_gles = context_ && context_->isOpenGLES(); + const int major = context_ ? context_->format().majorVersion() : 0; + const int minor = context_ ? context_->format().minorVersion() : 0; + const bool is_gles2 = is_gles && (major < 3); + const QString gles_preamble = is_gles2 + ? QStringLiteral("#version 100\n\n" + "precision highp float;\n\n" + "#define frag_color gl_FragColor\n") + : QStringLiteral("#version 300 es\n\n" + "precision highp float;\n\n"); + const QString desktop_preamble = // Use appropriate GL 3.2 shader header QStringLiteral("#version 150\n\n" "precision highp float;\n\n"); + const QString shader_preamble = is_gles ? gles_preamble : desktop_preamble; - QString complete_code; - - if (!code.startsWith(QStringLiteral("#version"))) { - complete_code = shader_preamble; - } - - if (code.isEmpty()) { + QString base_code = code; + if (base_code.isEmpty()) { // Use default code if (type == GL_FRAGMENT_SHADER) { - complete_code.append(FileFunctions::ReadFileAsString( - QStringLiteral(":/shaders/default.frag"))); + base_code = FileFunctions::ReadFileAsString( + QStringLiteral(":/shaders/default.frag")); } else if (type == GL_VERTEX_SHADER) { - complete_code.append(FileFunctions::ReadFileAsString( - QStringLiteral(":/shaders/default.vert"))); + base_code = FileFunctions::ReadFileAsString( + QStringLiteral(":/shaders/default.vert")); + } + } + + QString complete_code; + if (base_code.startsWith(QStringLiteral("#version"))) { + if (is_gles || !desktop_preamble.startsWith(QStringLiteral("#version"))) { + int newline = base_code.indexOf('\n'); + if (newline >= 0) { + complete_code = shader_preamble + base_code.mid(newline + 1); + } else { + complete_code = shader_preamble; + } + } else { + complete_code = base_code; } } else { - complete_code.append(code); + complete_code = shader_preamble + base_code; + } + + if (is_gles2) { + if (type == GL_VERTEX_SHADER) { + complete_code.replace(QRegularExpression(QStringLiteral("\\bin\\b")), + QStringLiteral("attribute")); + complete_code.replace(QRegularExpression(QStringLiteral("\\bout\\b")), + QStringLiteral("varying")); + } else if (type == GL_FRAGMENT_SHADER) { + complete_code.replace(QRegularExpression(QStringLiteral("\\bin\\b")), + QStringLiteral("varying")); + complete_code.replace(QRegularExpression( + QStringLiteral("\\bout\\s+vec4\\s+frag_color\\s*;")), + QStringLiteral("// frag_color output")); + complete_code.replace(QRegularExpression(QStringLiteral("\\btexture\\b")), + QStringLiteral("texture2D")); + } } QByteArray code_utf8 = complete_code.toUtf8(); diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 5fabbdcba..c2ad3d6b6 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -78,7 +78,7 @@ void TimeBasedWidget::SetScaleAndCenterOnPlayhead(const double &scale) ViewerOutput *TimeBasedWidget::GetConnectedNode() const { - return viewer_node_; + return viewer_node_.data(); } void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) @@ -89,10 +89,17 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) } // Set viewer node - ViewerOutput *old = viewer_node_; + ViewerOutput *old = viewer_node_.data(); viewer_node_ = node; - Current::getInstance().setCurrentVideoParams(viewer_node_->GetVideoParams()); - Current::getInstance().setCurrentAudioParams(viewer_node_->GetAudioParams()); + if (viewer_node_) { + Current::getInstance().setCurrentVideoParams( + viewer_node_->GetVideoParams()); + Current::getInstance().setCurrentAudioParams( + viewer_node_->GetAudioParams()); + } else { + Current::getInstance().setCurrentVideoParams(VideoParams()); + Current::getInstance().setCurrentAudioParams(AudioParams()); + } if (old) { // Call potential derivative functions for disconnecting the viewer node DisconnectNodeEvent(old); @@ -121,17 +128,17 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) // Call derivatives for (TimeBasedView *view : timeline_views_) { - view->SetViewerNode(viewer_node_); + view->SetViewerNode(viewer_node_.data()); } - ConnectedNodeChangeEvent(viewer_node_); + ConnectedNodeChangeEvent(viewer_node_.data()); if (viewer_node_) { // Connect length changed signal - connect(viewer_node_, &ViewerOutput::LengthChanged, this, + connect(viewer_node_.data(), &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll); - connect(viewer_node_, &ViewerOutput::RemovedFromGraph, this, + connect(viewer_node_.data(), &ViewerOutput::RemovedFromGraph, this, &TimeBasedWidget::ConnectedNodeRemovedFromGraph); - connect(viewer_node_, &ViewerOutput::PlayheadChanged, this, + connect(viewer_node_.data(), &ViewerOutput::PlayheadChanged, this, &TimeBasedWidget::PlayheadTimeChanged); // Connect ruler and scrollbar to timeline points @@ -141,14 +148,14 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) // If we're setting the timebase, set it automatically based on the video and audio parameters if (auto_set_timebase_) { AutoUpdateTimebase(); - connect(viewer_node_, &ViewerOutput::FrameRateChanged, this, + connect(viewer_node_.data(), &ViewerOutput::FrameRateChanged, this, &TimeBasedWidget::AutoUpdateTimebase); - connect(viewer_node_, &ViewerOutput::SampleRateChanged, this, + connect(viewer_node_.data(), &ViewerOutput::SampleRateChanged, this, &TimeBasedWidget::AutoUpdateTimebase); } // Call derivatives - ConnectNodeEvent(viewer_node_); + ConnectNodeEvent(viewer_node_.data()); } UpdateMaximumScroll(); @@ -271,6 +278,10 @@ void TimeBasedWidget::SendCatchUpScrollEvent() void TimeBasedWidget::AutoUpdateTimebase() { + if (!viewer_node_) { + SetTimebase(rational()); + return; + } rational video_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base(); @@ -448,7 +459,7 @@ void TimeBasedWidget::ZoomOut() void TimeBasedWidget::GoToPrevCut() { // Cuts are only possible in sequences - Sequence *sequence = dynamic_cast(viewer_node_); + Sequence *sequence = dynamic_cast(viewer_node_.data()); if (!sequence) { return; @@ -480,7 +491,7 @@ void TimeBasedWidget::GoToPrevCut() void TimeBasedWidget::GoToNextCut() { // Cuts are only possible in sequences - Sequence *sequence = dynamic_cast(viewer_node_); + Sequence *sequence = dynamic_cast(viewer_node_.data()); if (!sequence) { return; diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 8c10d1097..976fe7b62 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -19,6 +19,7 @@ #ifndef TIMEBASEDWIDGET_H #define TIMEBASEDWIDGET_H +#include #include #include "node/output/viewer/viewer.h" @@ -239,7 +240,7 @@ private: bool UserIsDraggingPlayhead() const; - ViewerOutput *viewer_node_; + QPointer viewer_node_; TimeRuler *ruler_; diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt index b235aa9d5..3896f93ca 100644 --- a/tests/gtest/CMakeLists.txt +++ b/tests/gtest/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(olive-gtest task_taskmanager_test.cpp module_smoke_test.cpp shader_resources_test.cpp + timebased_widget_test.cpp timeline_coordinate_test.cpp timeline_workarea_test.cpp ) diff --git a/tests/gtest/timebased_widget_test.cpp b/tests/gtest/timebased_widget_test.cpp new file mode 100644 index 000000000..d134528a5 --- /dev/null +++ b/tests/gtest/timebased_widget_test.cpp @@ -0,0 +1,21 @@ +#include + +#include "widget/timebased/timebasedwidget.h" +#include "node/output/viewer/viewer.h" + +TEST(TimeBasedWidget, ConnectViewerNodeNullSafe) +{ + olive::TimeBasedWidget widget(false, false); + widget.ConnectViewerNode(nullptr); + EXPECT_EQ(widget.GetConnectedNode(), nullptr); +} + +TEST(TimeBasedWidget, ConnectedNodeClearsOnDelete) +{ + olive::TimeBasedWidget widget(false, false); + auto *viewer = new olive::ViewerOutput(); + widget.ConnectViewerNode(viewer); + EXPECT_EQ(widget.GetConnectedNode(), viewer); + delete viewer; + EXPECT_EQ(widget.GetConnectedNode(), nullptr); +}