diff --git a/app/main.cpp b/app/main.cpp index f7f237326..e472c79d7 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -50,6 +50,9 @@ int main(int argc, char *argv[]) { format.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(format); + // Try to share OpenGL contexts + QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + // Create application instance QApplication a(argc, argv); diff --git a/app/panel/CMakeLists.txt b/app/panel/CMakeLists.txt index 5d90f94b5..0c6423398 100644 --- a/app/panel/CMakeLists.txt +++ b/app/panel/CMakeLists.txt @@ -21,6 +21,7 @@ add_subdirectory(node) add_subdirectory(param) add_subdirectory(pixelsampler) add_subdirectory(project) +add_subdirectory(scope) add_subdirectory(sequenceviewer) add_subdirectory(taskmanager) add_subdirectory(timebased) diff --git a/app/panel/footageviewer/footageviewer.cpp b/app/panel/footageviewer/footageviewer.cpp index 2ba54ff7c..61c77a3d1 100644 --- a/app/panel/footageviewer/footageviewer.cpp +++ b/app/panel/footageviewer/footageviewer.cpp @@ -28,7 +28,9 @@ FootageViewerPanel::FootageViewerPanel(QWidget *parent) : ViewerPanelBase(QStringLiteral("FootageViewerPanel"), parent) { // Set ViewerWidget as the central widget - SetTimeBasedWidget(new FootageViewerWidget()); + FootageViewerWidget* fvw = new FootageViewerWidget(); + connect(fvw, &FootageViewerWidget::RequestScopePanel, this, &FootageViewerPanel::CreateScopePanel); + SetTimeBasedWidget(fvw); // Set strings Retranslate(); diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index 372810d8b..b8f5d42e0 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -40,7 +40,9 @@ ProjectPanel::ProjectPanel(QWidget *parent) : // Create main widget and its layout QWidget* central_widget = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout(central_widget); - setWidget(central_widget); + layout->setMargin(0); + + SetWidgetWithPadding(central_widget); // Set up project toolbar ProjectToolbar* toolbar = new ProjectToolbar(this); diff --git a/app/panel/scope/CMakeLists.txt b/app/panel/scope/CMakeLists.txt new file mode 100644 index 000000000..2e456b060 --- /dev/null +++ b/app/panel/scope/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + panel/scope/scope.h + panel/scope/scope.cpp + PARENT_SCOPE +) diff --git a/app/panel/scope/scope.cpp b/app/panel/scope/scope.cpp new file mode 100644 index 000000000..4073a70af --- /dev/null +++ b/app/panel/scope/scope.cpp @@ -0,0 +1,100 @@ +/*** + + 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 . + +***/ + +#include "scope.h" + +#include + +#include "panel/viewer/viewer.h" + +OLIVE_NAMESPACE_ENTER + +ScopePanel::ScopePanel(QWidget* parent) : + PanelWidget(QStringLiteral("ScopePanel"), parent) +{ + QWidget* central = new QWidget(); + setWidget(central); + + QVBoxLayout* layout = new QVBoxLayout(central); + + QHBoxLayout* toolbar_layout = new QHBoxLayout(); + toolbar_layout->setMargin(0); + + scope_type_combobox_ = new QComboBox(); + + for (int i=0;iaddItem(QString()); + } + + toolbar_layout->addWidget(scope_type_combobox_); + toolbar_layout->addStretch(); + + layout->addLayout(toolbar_layout); + + stack_ = new QStackedWidget(); + layout->addWidget(stack_); + + // Create waveform view + waveform_view_ = new WaveformScope(); + stack_->addWidget(waveform_view_); + + // Create histogram + stack_->addWidget(new QWidget()); + + connect(scope_type_combobox_, static_cast(&QComboBox::currentIndexChanged), stack_, &QStackedWidget::setCurrentIndex); + + Retranslate(); +} + +void ScopePanel::SetType(ScopePanel::Type t) +{ + scope_type_combobox_->setCurrentIndex(t); +} + +QString ScopePanel::TypeToName(ScopePanel::Type t) +{ + switch (t) { + case kTypeWaveform: + return tr("Waveform"); + case kTypeHistogram: + return tr("Histogram"); + case kTypeCount: + break; + } + + return QString(); +} + +void ScopePanel::DrewManagedTexture(OpenGLTexture *texture) +{ + waveform_view_->SetTexture(texture); +} + +void ScopePanel::Retranslate() +{ + SetTitle(tr("Scope")); + + for (int i=0;isetItemText(i, TypeToName(static_cast(i))); + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/panel/scope/scope.h b/app/panel/scope/scope.h new file mode 100644 index 000000000..3256ad1fc --- /dev/null +++ b/app/panel/scope/scope.h @@ -0,0 +1,70 @@ +/*** + + 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 . + +***/ + +#ifndef SCOPE_PANEL_H +#define SCOPE_PANEL_H + +#include +#include + +#include "widget/panel/panel.h" +#include "widget/scope/waveform/waveform.h" + +OLIVE_NAMESPACE_ENTER + +class ViewerPanel; + +class ScopePanel : public PanelWidget +{ + Q_OBJECT +public: + enum Type { + kTypeWaveform, + kTypeHistogram, + + kTypeCount + }; + + ScopePanel(QWidget* parent = nullptr); + + void SetType(Type t); + + static QString TypeToName(Type t); + +public slots: + void DrewManagedTexture(OpenGLTexture* texture); + +protected: + virtual void Retranslate() override; + +private: + Type type_; + + QStackedWidget* stack_; + + QComboBox* scope_type_combobox_; + + WaveformScope* waveform_view_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // SCOPE_PANEL_H diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp index 912867110..4da1e95c0 100644 --- a/app/panel/timebased/timebased.cpp +++ b/app/panel/timebased/timebased.cpp @@ -70,7 +70,7 @@ void TimeBasedPanel::SetTimebase(const rational &timebase) void TimeBasedPanel::SetTime(const int64_t ×tamp) { - widget_->SetTime(timestamp); + widget_->SetTimestamp(timestamp); } void TimeBasedPanel::GoToPrevCut() diff --git a/app/panel/viewer/viewer.cpp b/app/panel/viewer/viewer.cpp index b45a14981..d2c3853e7 100644 --- a/app/panel/viewer/viewer.cpp +++ b/app/panel/viewer/viewer.cpp @@ -26,7 +26,9 @@ ViewerPanel::ViewerPanel(const QString &object_name, QWidget *parent) : ViewerPanelBase(object_name, parent) { // Set ViewerWidget as the central widget - SetTimeBasedWidget(new ViewerWidget()); + ViewerWidget* vw = new ViewerWidget(); + connect(vw, &ViewerWidget::RequestScopePanel, this, &ViewerPanel::CreateScopePanel); + SetTimeBasedWidget(vw); // Set strings Retranslate(); diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp index 1cd35960f..f53bb42bc 100644 --- a/app/panel/viewer/viewerbase.cpp +++ b/app/panel/viewer/viewerbase.cpp @@ -20,10 +20,13 @@ #include "viewerbase.h" +#include "window/mainwindow/mainwindow.h" + OLIVE_NAMESPACE_ENTER ViewerPanelBase::ViewerPanelBase(const QString& object_name, QWidget *parent) : - TimeBasedPanel(object_name, parent) + TimeBasedPanel(object_name, parent), + scope_panel_count_(0) { } @@ -88,4 +91,38 @@ void ViewerPanelBase::SetFullScreen(QScreen *screen) static_cast(GetTimeBasedWidget())->SetFullScreen(screen); } +void ViewerPanelBase::CreateScopePanel(ScopePanel::Type type) +{ + ViewerWidget* vw = static_cast(GetTimeBasedWidget()); + ScopePanel* p = Core::instance()->main_window()->AppendScopePanel(); + + p->SetType(type); + + // We treat our scope panels as kind of children, and destroy them if we're ever destroyed + connect(this, &ViewerPanelBase::destroyed, p, &ScopePanel::deleteLater); + + // If the scope closes, reduce the count (we do this because if no scopes are open, we can optimize the viewer slightly) + connect(p, &ScopePanel::CloseRequested, this, &ViewerPanelBase::ScopePanelClosed); + + // Connect viewer widget texture drawing to scope panel + connect(vw, &ViewerWidget::DrewManagedTexture, p, &ScopePanel::DrewManagedTexture); + + if (!scope_panel_count_) { + vw->SetEmitDrewManagedTextureEnabled(true); + } + + scope_panel_count_++; + + vw->ForceUpdate(); +} + +void ViewerPanelBase::ScopePanelClosed() +{ + scope_panel_count_--; + + if (!scope_panel_count_) { + static_cast(GetTimeBasedWidget())->SetEmitDrewManagedTextureEnabled(false); + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index dd593b066..7d551141d 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -56,6 +56,15 @@ public: */ void SetFullScreen(QScreen* screen = nullptr); +protected: + void CreateScopePanel(ScopePanel::Type type); + +private: + int scope_panel_count_; + +private slots: + void ScopePanelClosed(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/shaders/rgbwaveform.frag b/app/shaders/rgbwaveform.frag new file mode 100644 index 000000000..d2ad89940 --- /dev/null +++ b/app/shaders/rgbwaveform.frag @@ -0,0 +1,29 @@ +// Adapted from "RGB Waveform" by lebek +// https://www.shadertoy.com/view/4dK3Wc + +#version 110 + +uniform sampler2D ove_maintex; +uniform vec2 ove_resolution; +varying vec2 ove_texcoord; + +uniform float threshold; + +void main(void) { + vec3 col = vec3(0.0); + float s = ove_texcoord.y*1.8 - 0.15; + float maxb = s+threshold; + float minb = s-threshold; + + int y_lim = int(ove_resolution.y); + + for (int i = 0; i < y_lim; i++) { + vec3 x = texture2D(ove_maintex, vec2(ove_texcoord.x, float(i)/float(ove_resolution.y))).rgb; + col += step(x, vec3(maxb))*step(vec3(minb), x) / (ove_resolution.y * 0.125); + + float l = dot(x, x); + col += step(l, maxb*maxb)*step(minb*minb, l) / (ove_resolution.y * 0.125); + } + + gl_FragColor = vec4(col, 1.0); +} diff --git a/app/shaders/shaders.qrc b/app/shaders/shaders.qrc index 3efe572cc..ab4e87535 100644 --- a/app/shaders/shaders.qrc +++ b/app/shaders/shaders.qrc @@ -12,6 +12,7 @@ dropshadow.xml diptoblack.frag diptoblack.xml + rgbwaveform.frag solid.frag solid.xml stroke.frag diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index fbce6118a..4dce10689 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -34,6 +34,7 @@ add_subdirectory(playbackcontrols) add_subdirectory(projectexplorer) add_subdirectory(projecttoolbar) add_subdirectory(resizablescrollbar) +add_subdirectory(scope) add_subdirectory(slider) add_subdirectory(taskview) add_subdirectory(timebased) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 3660a2ba5..6b57f2c14 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -79,7 +79,7 @@ NodeParamView::NodeParamView(QWidget *parent) : // Connect ruler and keyframe view together connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime); connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime); - connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime); + connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTimestamp); // Connect keyframe view scaling to this connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale); diff --git a/app/widget/scope/CMakeLists.txt b/app/widget/scope/CMakeLists.txt new file mode 100644 index 000000000..a14b351dd --- /dev/null +++ b/app/widget/scope/CMakeLists.txt @@ -0,0 +1,23 @@ +# 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 . + +add_subdirectory(histogram) +add_subdirectory(waveform) + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + PARENT_SCOPE +) diff --git a/app/widget/scope/histogram/CMakeLists.txt b/app/widget/scope/histogram/CMakeLists.txt new file mode 100644 index 000000000..3204a53d7 --- /dev/null +++ b/app/widget/scope/histogram/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + widget/scope/histogram/histogram.h + widget/scope/histogram/histogram.cpp + PARENT_SCOPE +) diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp new file mode 100644 index 000000000..1f4a13543 --- /dev/null +++ b/app/widget/scope/histogram/histogram.cpp @@ -0,0 +1,42 @@ +/*** + + 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 . + +***/ + +#include "histogram.h" + +OLIVE_NAMESPACE_ENTER + +HistogramScope::HistogramScope(QWidget* parent) : + QOpenGLWidget(parent) +{ +} + +void HistogramScope::SetBuffer(Frame* frame) +{ + buffer_ = frame; + + update(); +} + +void HistogramScope::paintGL() +{ + //QPainter p(this); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/scope/histogram/histogram.h b/app/widget/scope/histogram/histogram.h new file mode 100644 index 000000000..973bb1f94 --- /dev/null +++ b/app/widget/scope/histogram/histogram.h @@ -0,0 +1,49 @@ +/*** + + 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 . + +***/ + +#ifndef HISTOGRAMSCOPE_H +#define HISTOGRAMSCOPE_H + +#include + +#include "codec/frame.h" + +OLIVE_NAMESPACE_ENTER + +class HistogramScope : public QOpenGLWidget +{ + Q_OBJECT +public: + HistogramScope(QWidget* parent = nullptr); + +public slots: + void SetBuffer(Frame* frame); + +protected: + virtual void paintGL() override; + +private: + Frame* buffer_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // HISTOGRAMSCOPE_H diff --git a/app/widget/scope/waveform/CMakeLists.txt b/app/widget/scope/waveform/CMakeLists.txt new file mode 100644 index 000000000..61a980b8f --- /dev/null +++ b/app/widget/scope/waveform/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + widget/scope/waveform/waveform.h + widget/scope/waveform/waveform.cpp + PARENT_SCOPE +) diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp new file mode 100644 index 000000000..caab7c9fb --- /dev/null +++ b/app/widget/scope/waveform/waveform.cpp @@ -0,0 +1,82 @@ + +/*** + + 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 . + +***/ + +#include "waveform.h" + +#include "node/node.h" +#include "render/backend/opengl/openglrenderfunctions.h" + +OLIVE_NAMESPACE_ENTER + +WaveformScope::WaveformScope(QWidget* parent) : + QOpenGLWidget(parent), + texture_(nullptr) +{ + +} + +void WaveformScope::SetTexture(OpenGLTexture *texture) +{ + texture_ = texture; + + update(); +} + +void WaveformScope::initializeGL() +{ + pipeline_ = OpenGLShader::Create(); + pipeline_->create(); + pipeline_->addShaderFromSourceCode(QOpenGLShader::Vertex, OpenGLShader::CodeDefaultVertex()); + pipeline_->addShaderFromSourceCode(QOpenGLShader::Fragment, Node::ReadFileAsString(":/shaders/rgbwaveform.frag")); + pipeline_->link(); +} + +void WaveformScope::paintGL() +{ + context()->functions()->glClearColor(0, 0, 0, 0); + context()->functions()->glClear(GL_COLOR_BUFFER_BIT); + + if (!pipeline_ || !texture_) { + return; + } + + pipeline_->bind(); + pipeline_->setUniformValue("ove_resolution", texture_->width(), texture_->height()); + + // The general size of a pixel + pipeline_->setUniformValue("threshold", 2.0f / static_cast(height())); + + pipeline_->release(); + + texture_->Bind(); + + OpenGLRenderFunctions::Blit(pipeline_); + + texture_->Release(); +} + +void WaveformScope::CleanUp() +{ + pipeline_ = nullptr; + texture_ = nullptr; +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/scope/waveform/waveform.h b/app/widget/scope/waveform/waveform.h new file mode 100644 index 000000000..787717e9d --- /dev/null +++ b/app/widget/scope/waveform/waveform.h @@ -0,0 +1,59 @@ +/*** + + 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 . + +***/ + +#ifndef WAVEFORMSCOPE_H +#define WAVEFORMSCOPE_H + +#include + +#include "codec/frame.h" +#include "render/backend/opengl/openglcolorprocessor.h" +#include "render/backend/opengl/openglshader.h" +#include "render/backend/opengl/opengltexture.h" + +OLIVE_NAMESPACE_ENTER + +class WaveformScope : public QOpenGLWidget +{ + Q_OBJECT +public: + WaveformScope(QWidget* parent = nullptr); + +public slots: + void SetTexture(OpenGLTexture* texture); + +protected: + virtual void initializeGL() override; + + virtual void paintGL() override; + +private: + OpenGLShaderPtr pipeline_; + + OpenGLTexture* texture_; + +private slots: + void CleanUp(); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // WAVEFORMSCOPE_H diff --git a/app/widget/timebased/timebased.cpp b/app/widget/timebased/timebased.cpp index d51bab8c9..e0afa2647 100644 --- a/app/widget/timebased/timebased.cpp +++ b/app/widget/timebased/timebased.cpp @@ -204,7 +204,7 @@ void TimeBasedWidget::ConnectTimelineView(TimelineViewBase *base) timeline_views_.append(base); } -void TimeBasedWidget::SetTime(int64_t timestamp) +void TimeBasedWidget::SetTimestamp(int64_t timestamp) { ruler_->SetTime(timestamp); @@ -325,7 +325,7 @@ void TimeBasedWidget::GoToEnd() void TimeBasedWidget::SetTimeAndSignal(const int64_t &t) { - SetTime(t); + SetTimestamp(t); emit TimeChanged(t); } diff --git a/app/widget/timebased/timebased.h b/app/widget/timebased/timebased.h index 596f6ffe6..896a00481 100644 --- a/app/widget/timebased/timebased.h +++ b/app/widget/timebased/timebased.h @@ -53,8 +53,7 @@ public: void SetScaleAndCenterOnPlayhead(const double& scale); public slots: - // FIXME: Rename this to SetTimestamp to reduce confusion - void SetTime(int64_t timestamp); + void SetTimestamp(int64_t timestamp); void SetTimebase(const rational& timebase); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 1db2435cc..4b8bae2ee 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -37,6 +37,7 @@ #include "project/project.h" #include "render/pixelformat.h" #include "widget/menu/menu.h" +#include "window/mainwindow/mainwindow.h" OLIVE_NAMESPACE_ENTER @@ -64,6 +65,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) : ViewerGLWidget* main_widget = new ViewerGLWidget(); connect(main_widget, &ViewerGLWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); connect(main_widget, &ViewerGLWidget::CursorColor, this, &ViewerWidget::CursorColor); + connect(main_widget, &ViewerGLWidget::LoadedBuffer, this, &ViewerWidget::LoadedBuffer); + connect(main_widget, &ViewerGLWidget::LoadedTexture, this, &ViewerWidget::LoadedTexture); + connect(main_widget, &ViewerGLWidget::DrewManagedTexture, this, &ViewerWidget::DrewManagedTexture); connect(sizer_, &ViewerSizer::RequestMatrix, main_widget, &ViewerGLWidget::SetMatrix); sizer_->SetWidget(main_widget); gl_widgets_.append(main_widget); @@ -259,7 +263,7 @@ void ViewerWidget::ConnectViewerNode(ViewerOutput *node, ColorManager* color_man TimeBasedWidget::ConnectViewerNode(node); // Set texture to new texture (or null if no viewer node is available) - UpdateTextureFromNode(GetTime()); + ForceUpdate(); } void ViewerWidget::SetColorMenuEnabled(bool enabled) @@ -313,6 +317,12 @@ void ViewerWidget::SetFullScreen(QScreen *screen) gl_widgets_.append(vw->gl_widget()); } +void ViewerWidget::ForceUpdate() +{ + // Hack that forces the viewer to update + UpdateTextureFromNode(GetTime()); +} + VideoRenderBackend *ViewerWidget::video_renderer() const { return video_renderer_; @@ -458,7 +468,6 @@ void ViewerWidget::ContextMenuSetCustomSafeMargins() if (ratio_components.size() == 2) { bool numer_ok, denom_ok; - // FIXME: Won't accept decimals like 2.39:1 double num = ratio_components.at(0).toDouble(&numer_ok); double den = ratio_components.at(1).toDouble(&denom_ok); @@ -486,6 +495,11 @@ void ViewerWidget::WindowAboutToClose() gl_widgets_.removeAll(vw->gl_widget()); } +void ViewerWidget::ContextMenuScopeTriggered(QAction *action) +{ + emit RequestScopePanel(static_cast(action->data().toInt())); +} + void ViewerWidget::UpdateRendererParameters() { if (!GetConnectedNode()) { @@ -626,6 +640,21 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) menu.addSeparator(); + { + // Scopes + Menu* scopes_menu = new Menu(tr("Scopes"), &menu); + menu.addMenu(scopes_menu); + + for (int i=0;iaddAction(ScopePanel::TypeToName(static_cast(i))); + scope_action->setData(i); + } + + connect(scopes_menu, &Menu::triggered, this, &ViewerWidget::ContextMenuScopeTriggered); + } + + menu.addSeparator(); + { // Safe Margins Menu* safe_margin_menu = new Menu(tr("Safe Margins"), &menu); @@ -768,6 +797,11 @@ void ViewerWidget::SetSignalCursorColorEnabled(bool e) } } +void ViewerWidget::SetEmitDrewManagedTextureEnabled(bool e) +{ + main_gl_widget()->SetEmitDrewManagedTextureEnabled(e); +} + void ViewerWidget::TimebaseChangedEvent(const rational &timebase) { TimeBasedWidget::TimebaseChangedEvent(timebase); @@ -852,7 +886,7 @@ 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()); + ForceUpdate(); } } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 1396f9a9c..4fdd590b8 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -31,6 +31,7 @@ #include "audiowaveformview.h" #include "common/rational.h" #include "node/output/viewer/viewer.h" +#include "panel/scope/scope.h" #include "render/backend/opengl/openglbackend.h" #include "render/backend/opengl/opengltexture.h" #include "render/backend/audio/audiobackend.h" @@ -80,6 +81,8 @@ public: */ void SetFullScreen(QScreen* screen = nullptr); + void ForceUpdate(); + VideoRenderBackend* video_renderer() const; public slots: @@ -123,12 +126,39 @@ public slots: */ void SetSignalCursorColorEnabled(bool e); + /** + * @brief Wrapper for ViewerGLWidget::SetEmitDrewManagedTextureEnabled() + */ + void SetEmitDrewManagedTextureEnabled(bool e); + signals: /** * @brief Wrapper for ViewerGLWidget::CursorColor() */ void CursorColor(const Color& reference, const Color& display); + /** + * @brief Wrapper for ViewerGLWidget::LoadedBuffer() + */ + void LoadedBuffer(Frame* load_buffer); + + /** + * @brief Wrapper for ViewerGLWidget::LoadedTexture() + */ + void LoadedTexture(OpenGLTexture* texture); + + /** + * @brief Wrapper for ViewerGLWidget::DrewManagedTexture() + */ + void DrewManagedTexture(OpenGLTexture* texture); + + /** + * @brief Request a scope panel + * + * As a widget, we don't handle panels, but a parent panel may pick this signal up. + */ + void RequestScopePanel(ScopePanel::Type type); + protected: virtual void TimebaseChangedEvent(const rational &) override; virtual void TimeChangedEvent(const int64_t &) override; @@ -244,6 +274,8 @@ private slots: void WindowAboutToClose(); + void ContextMenuScopeTriggered(QAction* action); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/viewer/viewerglwidget.cpp b/app/widget/viewer/viewerglwidget.cpp index ac6ea5a50..29f65f332 100644 --- a/app/widget/viewer/viewerglwidget.cpp +++ b/app/widget/viewer/viewerglwidget.cpp @@ -42,9 +42,11 @@ bool ViewerGLWidget::nouveau_check_done_ = false; ViewerGLWidget::ViewerGLWidget(QWidget *parent) : QOpenGLWidget(parent), + managed_copy_pipeline_(nullptr), color_manager_(nullptr), has_image_(false), - signal_cursor_color_(false) + signal_cursor_color_(false), + enable_display_referred_signal_(false) { setContextMenuPolicy(Qt::CustomContextMenu); } @@ -115,9 +117,11 @@ void ViewerGLWidget::SetImage(const QString &fn) input->read_image(input->spec().format, load_buffer_.data()); input->close(); + emit LoadedBuffer(&load_buffer_); + texture_.Upload(load_buffer_.data()); - emit LoadedBuffer(&load_buffer_); + emit LoadedTexture(&texture_); doneCurrent(); @@ -169,6 +173,18 @@ void ViewerGLWidget::SetImageFromLoadBuffer(Frame *in_buffer) update(); } +void ViewerGLWidget::SetEmitDrewManagedTextureEnabled(bool e) +{ + enable_display_referred_signal_ = e; + + if (!enable_display_referred_signal_) { + // Destroy the texture now + managed_texture_.Destroy(); + managed_copy_pipeline_ = nullptr; + framebuffer_.Destroy(); + } +} + void ViewerGLWidget::SetOCIODisplay(const QString &display) { ocio_display_ = display; @@ -310,6 +326,34 @@ void ViewerGLWidget::paintGL() // We only draw if we have a pipeline if (has_image_ && color_service_ && texture_.IsCreated()) { + + // If we're distributing our display-referred final buffer, we'll have to make a copy of it + if (enable_display_referred_signal_) { + + if (!managed_texture_.IsCreated() + || managed_texture_.width() != texture_.width() + || managed_texture_.height() != texture_.height() + || managed_texture_.format() != texture_.format()) { + managed_texture_.Destroy(); + + managed_texture_.Create(context(), texture_.width(), texture_.height(), texture_.format()); + } + + if (!managed_copy_pipeline_) { + managed_copy_pipeline_ = OpenGLShader::CreateDefault(); + } + + if (!framebuffer_.IsCreated()) { + framebuffer_.Create(context()); + } + + framebuffer_.Attach(&managed_texture_); + framebuffer_.Bind(); + + context()->functions()->glViewport(0, 0, managed_texture_.width(), managed_texture_.height()); + + } + // Bind retrieved texture f->glBindTexture(GL_TEXTURE_2D, texture_.texture()); @@ -318,6 +362,25 @@ void ViewerGLWidget::paintGL() // Release retrieved texture f->glBindTexture(GL_TEXTURE_2D, 0); + + if (enable_display_referred_signal_) { + + framebuffer_.Release(); + framebuffer_.Detach(); + + emit DrewManagedTexture(&managed_texture_); + + // Bind retrieved texture + managed_texture_.Bind(); + + context()->functions()->glViewport(0, 0, width(), height()); + + OpenGLRenderFunctions::Blit(managed_copy_pipeline_); + + // Bind retrieved texture + managed_texture_.Release(); + + } } // Draw action/title safe areas @@ -433,7 +496,10 @@ void ViewerGLWidget::ContextCleanup() makeCurrent(); color_service_ = nullptr; + managed_copy_pipeline_ = nullptr; texture_.Destroy(); + managed_texture_.Destroy(); + framebuffer_.Destroy(); doneCurrent(); } diff --git a/app/widget/viewer/viewerglwidget.h b/app/widget/viewer/viewerglwidget.h index 4898db3a8..704145c69 100644 --- a/app/widget/viewer/viewerglwidget.h +++ b/app/widget/viewer/viewerglwidget.h @@ -148,6 +148,15 @@ public slots: */ void SetImageFromLoadBuffer(Frame* in_buffer); + /** + * @brief Enables or disables DrewManagedTexture() + * + * To emit a display referred texture, it needs to be copied after the color transform is complete. This naturally + * adds extra GPU cycles that are wasted if there's nothing receiving the signal. Therefore, the signal is disabled + * by default. + */ + void SetEmitDrewManagedTextureEnabled(bool e); + signals: /** * @brief Signal emitted when the user starts dragging from the viewer @@ -160,10 +169,26 @@ signals: void CursorColor(const Color& reference, const Color& display); /** - * @brief Connect this to the SetImageFromLoadBuffer() slot of another ViewerGLWidget to show the same thing + * @brief Signal emitted when a buffer is loaded from file into memory + * + * This buffer will be the direct output of the renderer in reference space in CPU memory. + * + * Connect this to the SetImageFromLoadBuffer() slot of another ViewerGLWidget to show the same thing */ void LoadedBuffer(Frame* load_buffer); + /** + * @brief Signal emitted when a buffer is loaded into a texture + * + * This texture will be the direct output of the renderer in reference space in GPU VRAM. + */ + void LoadedTexture(OpenGLTexture* texture); + + /** + * @brief Emitted when the a texture has been transformed to display + */ + void DrewManagedTexture(OpenGLTexture* texture); + protected: /** * @brief Override the mouse press event simply to emit the DragStarted() signal @@ -220,6 +245,23 @@ private: */ OpenGLTexture texture_; + /** + * @brief Internal framebuffer used to draw to managed_texture_ + */ + OpenGLFramebuffer framebuffer_; + + /** + * @brief Internal referenceto the OpenGL texture that's been managed + * + * Kept so that scopes can use the display-referred buffer without having to transform again. + */ + OpenGLTexture managed_texture_; + + /** + * @brief Pipeline used to draw to managed_texture_ + */ + OpenGLShaderPtr managed_copy_pipeline_; + /** * @brief Connected color manager */ @@ -250,6 +292,8 @@ private: ViewerSafeMarginInfo safe_margin_; + bool enable_display_referred_signal_; + private slots: /** * @brief Slot to connect just before the OpenGL context is destroyed to clean up resources diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index e8f628cb6..9bef78fb9 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -244,6 +244,21 @@ void MainWindow::FolderOpen(Project* p, Item *i, bool floating) folder_panels_.append(panel); } +ScopePanel *MainWindow::AppendScopePanel() +{ + ScopePanel* panel = PanelManager::instance()->CreatePanel(this); + + SetUniquePanelID(panel, scope_panels_); + + panel->setFloating(true); + panel->show(); + + panel->SetSignalInsteadOfClose(true); + connect(panel, &ScopePanel::CloseRequested, this, &MainWindow::ScopeCloseRequested); + + return panel; +} + void MainWindow::SetFullscreen(bool fullscreen) { if (fullscreen) { @@ -408,6 +423,14 @@ void MainWindow::FolderCloseRequested() panel->deleteLater(); } +void MainWindow::ScopeCloseRequested() +{ + ScopePanel* panel = static_cast(sender()); + + scope_panels_.removeOne(panel); + panel->deleteLater(); +} + void MainWindow::LoadLayoutInternal(QXmlStreamReader *reader, XMLNodeData *xml_data) { while (XMLReadNextStartElement(reader)) { diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 61a3c1b69..4f2d52c6e 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -29,6 +29,7 @@ #include "panel/node/node.h" #include "panel/param/param.h" #include "panel/project/project.h" +#include "panel/scope/scope.h" #include "panel/taskmanager/taskmanager.h" #include "panel/timeline/timeline.h" #include "panel/tool/tool.h" @@ -63,14 +64,16 @@ public: bool IsSequenceOpen(Sequence* sequence) const; + void FolderOpen(Project* p, Item* i, bool floating); + + ScopePanel* AppendScopePanel(); + #ifdef Q_OS_WINDOWS void SetTaskbarButtonState(TBPFLAG flags); void SetTaskbarButtonProgress(int value, int max); #endif - void FolderOpen(Project* p, Item* i, bool floating); - public slots: void ProjectOpen(Project *p); @@ -121,6 +124,7 @@ private: TaskManagerPanel* task_man_panel_; CurvePanel* curve_panel_; PixelSamplerPanel* pixel_sampler_panel_; + QList scope_panels_; #ifdef Q_OS_WINDOWS unsigned int taskbar_btn_id_; @@ -139,6 +143,8 @@ private slots: void FolderCloseRequested(); + void ScopeCloseRequested(); + void LoadLayoutInternal(QXmlStreamReader* reader, XMLNodeData *xml_data); };