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);
};