From ec8b046f02586653083050da5bdb09b88db133e0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 11 Aug 2020 02:02:50 +1000 Subject: [PATCH] viewer: implemented basic deinterlace Implements a very basic GLSL deinterlace that simply halves the vertical resolution and then interpolates between the fields. This can be toggled on or off. The reasons for being so basic is: - Speed, very quick code running in OpenGL - It would seem the highest quality deinterlacers are temporally based which doesn't make much sense for the viewer, particularly since we can't double the frame rate since our timecode is fixed to the frames. Higher quality interlacing/deinterlacing will be present in the actual renderer. --- app/node/output/viewer/viewer.cpp | 5 ++ app/node/output/viewer/viewer.h | 2 + app/render/backend/opengl/openglshader.cpp | 54 +++++++++++----------- app/render/backend/opengl/openglshader.h | 2 +- app/widget/viewer/viewer.cpp | 22 +++++++++ app/widget/viewer/viewer.h | 2 + app/widget/viewer/viewerdisplay.cpp | 15 +++++- app/widget/viewer/viewerdisplay.h | 12 +++++ 8 files changed, 85 insertions(+), 29 deletions(-) diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index c682fb8a6..577c7d15d 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -137,6 +137,7 @@ void ViewerOutput::set_video_params(const VideoParams &video) bool size_changed = video_params_.width() != video.width() || video_params_.height() != video.height(); bool timebase_changed = video_params_.time_base() != video.time_base(); bool pixel_aspect_changed = video_params_.pixel_aspect_ratio() != video.pixel_aspect_ratio(); + bool interlacing_changed = video_params_.interlacing() != video.interlacing(); video_params_ = video; @@ -148,6 +149,10 @@ void ViewerOutput::set_video_params(const VideoParams &video) emit PixelAspectChanged(video_params_.pixel_aspect_ratio()); } + if (interlacing_changed) { + emit InterlacingChanged(video_params_.interlacing()); + } + if (timebase_changed) { video_frame_cache_.SetTimebase(video_params_.time_base()); emit TimebaseChanged(video_params_.time_base()); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 2d67c9800..96c30cdf9 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -133,6 +133,8 @@ signals: void PixelAspectChanged(const rational& pixel_aspect); + void InterlacingChanged(VideoParams::Interlacing mode); + void VideoParamsChanged(); void AudioParamsChanged(); diff --git a/app/render/backend/opengl/openglshader.cpp b/app/render/backend/opengl/openglshader.cpp index ff75ad189..7e76f619a 100644 --- a/app/render/backend/opengl/openglshader.cpp +++ b/app/render/backend/opengl/openglshader.cpp @@ -146,8 +146,9 @@ OpenGLShaderPtr OpenGLShader::CreateOCIO(QOpenGLContext* ctx, return shader; } -QString OpenGLShader::CodeDefaultFragment(const QString &function_name, const QString &shader_code) +QString OpenGLShader::CodeDefaultFragment(QString function_name, const QString &shader_code) { + // Create shader header QString frag_code = QStringLiteral("#version 150\n" "\n" "#ifdef GL_ES\n" @@ -156,48 +157,47 @@ QString OpenGLShader::CodeDefaultFragment(const QString &function_name, const QS "#endif\n" "\n" "uniform sampler2D ove_maintex;\n" - "uniform bool color_only;\n" - "uniform vec4 color_only_color;\n" + "uniform vec2 ove_resolution;\n" + "uniform bool ove_deinterlace;\n" "\n" "in vec2 ove_texcoord;\n" "\n" "out vec4 fragColor;\n" "\n"); - // Finish the function with the main function - // Check if additional code was passed to this function, add it here - if (shader_code.isEmpty()) { - - // If not, just add a pure main() function - - frag_code.append(QStringLiteral("\n" - "void main() {\n" - " if (color_only) {\n" - " fragColor = color_only_color;" - " } else {\n" - " vec4 color = texture(ove_maintex, ove_texcoord);\n" - " fragColor = color;\n" - " }\n" - "}\n")); - - } else { + if (!function_name.isEmpty() && !shader_code.isEmpty()) { // If additional code was passed, add it and reference it in main(). // - // The function in the additional code is expected to be `vec4 function_name(vec4 color)`. The texture coordinate - // can be acquired through `ove_texcoord`. + // The function in the additional code is expected to be `vec4 function_name(vec4 color)`. + // The texture coordinate can be acquired through `ove_texcoord`. frag_code.append(shader_code); - frag_code.append(QStringLiteral("\n" - "void main() {\n" - " vec4 color = %1(texture(ove_maintex, ove_texcoord));\n" - " fragColor = color;\n" - "}\n").arg(function_name)); + } else { + + // No function to call + function_name = QString(); } + // Our function_name arg will either resolve to the function added to this or to nothing, in + // which case they'll just be benign brackets. + frag_code.append(QStringLiteral("\n" + "void main() {\n" + " vec2 using_texcoord = ove_texcoord;\n" + " if (ove_deinterlace) {\n" + " // A very basic deinterlace that halves the vertical\n" + " // resolution and linearly interpolates the two fields\n" + " // by reading the texture coord between them.\n" + " float half_vert = round(ove_resolution.y / 2.0);\n" + " using_texcoord.y = (round(using_texcoord.y * half_vert) + 0.25) / half_vert;\n" + " }\n" + " vec4 color = %1(texture(ove_maintex, using_texcoord));\n" + " fragColor = color;\n" + "}\n").arg(function_name)); + return frag_code; } diff --git a/app/render/backend/opengl/openglshader.h b/app/render/backend/opengl/openglshader.h index 3adab00d4..452dc3c3d 100644 --- a/app/render/backend/opengl/openglshader.h +++ b/app/render/backend/opengl/openglshader.h @@ -51,7 +51,7 @@ public: OCIO::ConstProcessorRcPtr processor, bool alpha_is_associated); - static QString CodeDefaultFragment(const QString &function_name = QString(), + static QString CodeDefaultFragment(QString function_name = QString(), const QString &shader_code = QString()); static QString CodeDefaultVertex(); static QString CodeAlphaDisassociate(const QString& function_name); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 62d42aed4..f6a71f333 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -167,12 +167,15 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SetViewerResolution); connect(n, &ViewerOutput::PixelAspectChanged, this, &ViewerWidget::SetViewerPixelAspect); connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); + connect(n, &ViewerOutput::InterlacingChanged, this, &ViewerWidget::InterlacingChangedSlot); connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters); connect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); connect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); connect(n->video_frame_cache(), &FrameHashCache::Shifted, this, &ViewerWidget::ViewerShiftedRange); connect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack); + InterlacingChangedSlot(n->video_params().interlacing()); + ruler()->SetPlaybackCache(n->video_frame_cache()); n->audio_playback_cache()->SetParameters(n->audio_params()); @@ -218,6 +221,7 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) disconnect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SetViewerResolution); disconnect(n, &ViewerOutput::PixelAspectChanged, this, &ViewerWidget::SetViewerPixelAspect); disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot); + disconnect(n, &ViewerOutput::InterlacingChanged, this, &ViewerWidget::InterlacingChangedSlot); disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters); disconnect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); @@ -824,6 +828,16 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) connect(full_screen_menu, &QMenu::triggered, this, &ViewerWidget::ContextMenuSetFullScreen); } + { + // Deinterlace Option + if (GetConnectedNode()->video_params().interlacing() != VideoParams::kInterlaceNone) { + QAction* deinterlace_action = menu.addAction(tr("Deinterlace")); + deinterlace_action->setCheckable(true); + deinterlace_action->setChecked(display_widget_->IsDeinterlacing()); + connect(deinterlace_action, &QAction::triggered, display_widget_, &ViewerDisplayWidget::SetDeinterlacing); + } + } + menu.addSeparator(); { @@ -1089,6 +1103,14 @@ void ViewerWidget::LengthChangedSlot(const rational &length) } } +void ViewerWidget::InterlacingChangedSlot(VideoParams::Interlacing interlacing) +{ + // Automatically set a "sane" deinterlacing option + display_widget_->SetDeinterlacing(interlacing != VideoParams::kInterlaceNone); + + // FIXME: Set windows too +} + void ViewerWidget::UpdateRendererVideoParameters() { renderer_->ClearVideoQueue(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 419aa8c65..5edc2821f 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -261,6 +261,8 @@ private slots: void LengthChangedSlot(const rational& length); + void InterlacingChangedSlot(VideoParams::Interlacing interlacing); + void UpdateRendererVideoParameters(); void UpdateRendererAudioParameters(); diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 943314ef7..7aa6db6fe 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -43,7 +43,8 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : signal_cursor_color_(false), gizmos_(nullptr), gizmo_click_(false), - last_loaded_buffer_(nullptr) + last_loaded_buffer_(nullptr), + deinterlace_(false) { } @@ -86,6 +87,12 @@ void ViewerDisplayWidget::SetImage(FramePtr in_buffer) update(); } +void ViewerDisplayWidget::SetDeinterlacing(bool e) +{ + deinterlace_ = e; + update(); +} + const ViewerSafeMarginInfo &ViewerDisplayWidget::GetSafeMargin() const { return safe_margin_; @@ -212,6 +219,12 @@ void ViewerDisplayWidget::paintGL() // Bind retrieved texture f->glBindTexture(GL_TEXTURE_2D, texture_.texture()); + // Set some parameters + color_service()->pipeline()->bind(); + color_service()->pipeline()->setUniformValue("ove_resolution", texture_.width(), texture_.height()); + color_service()->pipeline()->setUniformValue("ove_deinterlace", deinterlace_); + color_service()->pipeline()->release(); + // Blit using the color service color_service()->ProcessOpenGL(true, matrix_); diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index f8db608ef..7a240beff 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -77,6 +77,11 @@ public: FramePtr last_loaded_buffer() const; + bool IsDeinterlacing() const + { + return deinterlace_; + } + public slots: /** * @brief Set the transformation matrix to draw with @@ -102,6 +107,11 @@ public slots: */ void SetImage(FramePtr in_buffer); + /** + * @brief Enables/disables a basic deinterlace on the viewer + */ + void SetDeinterlacing(bool e); + signals: /** * @brief Signal emitted when the user starts dragging from the viewer @@ -174,6 +184,8 @@ private: FramePtr last_loaded_buffer_; + bool deinterlace_; + private slots: /** * @brief Slot to connect just before the OpenGL context is destroyed to clean up resources