summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/wx/film_viewer.cc8
-rw-r--r--src/wx/gl_meters.cc401
-rw-r--r--src/wx/gl_meters.h88
-rw-r--r--src/wx/gl_util.cc58
-rw-r--r--src/wx/gl_util.h16
-rw-r--r--src/wx/gl_video_view.h7
-rw-r--r--src/wx/simple_video_view.h2
-rw-r--r--src/wx/video_view.h2
-rw-r--r--src/wx/wscript1
9 files changed, 576 insertions, 7 deletions
diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc
index a52f4286e..0c144086b 100644
--- a/src/wx/film_viewer.cc
+++ b/src/wx/film_viewer.cc
@@ -98,20 +98,16 @@ FilmViewer::FilmViewer(wxWindow* p)
_video_view = std::make_shared<GLVideoView>(this, p);
break;
case Config::VIDEO_VIEW_SIMPLE:
- {
- auto simple = std::make_shared<SimpleVideoView>(this, p);
- _meters_dialog->set_meters(simple->get_meters());
- _video_view = simple;
- }
+ _video_view = std::make_shared<SimpleVideoView>(this, p);
break;
}
#else
auto simple = std::make_shared<SimpleVideoView>(this, p);
- _meters_dialog->set_meters(simple->get_meters());
_video_view = simple;
#endif
_meters_dialog->Bind(wxEVT_SHOW, boost::bind(&FilmViewer::meters_shown, this, _1));
+ _meters_dialog->set_meters(_video_view->get_meters());
_video_view->Sized.connect(boost::bind(&FilmViewer::video_view_sized, this));
_video_view->TooManyDropped.connect(boost::bind(boost::ref(TooManyDropped)));
diff --git a/src/wx/gl_meters.cc b/src/wx/gl_meters.cc
new file mode 100644
index 000000000..74fb49be0
--- /dev/null
+++ b/src/wx/gl_meters.cc
@@ -0,0 +1,401 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "film_viewer.h"
+#include "gl_meters.h"
+#include "gl_util.h"
+#include "meter_util.h"
+#include "wx_util.h"
+#include "lib/butler.h"
+#include "lib/constants.h"
+#include "lib/image_png.h"
+LIBDCP_DISABLE_WARNINGS
+#include <wx/dcmemory.h>
+#include <wx/glcanvas.h>
+#include <wx/graphics.h>
+#include <wx/rawbmp.h>
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+
+#ifdef DCPOMATIC_OSX
+#define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED
+#include <OpenGL/OpenGL.h>
+#include <OpenGL/gl3.h>
+#endif
+
+#ifdef DCPOMATIC_LINUX
+#include <GL/glu.h>
+#include <GL/glext.h>
+#endif
+
+#ifdef DCPOMATIC_WINDOWS
+#include <GL/glu.h>
+#include <GL/wglext.h>
+#endif
+
+
+using std::make_shared;
+using std::vector;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+using namespace dcpomatic::gl;
+
+
+static constexpr char fragment_source[] =
+"#version 330 core\n"
+"\n"
+"in vec2 TexCoord;\n"
+"\n"
+"uniform sampler2D texture_sampler;\n"
+"\n"
+/* type = 0: draw meter bar
+ * type = 1: draw label texture
+ * type = 2: draw scale lines
+ * See FragmentType enum below.
+ */
+"uniform int type = 0;\n"
+"uniform vec4 meter_bar_colour;\n"
+"uniform vec4 meter_line_colour;\n"
+"uniform int height;\n"
+"\n"
+"out vec4 FragColor;\n"
+"\n"
+"void main()\n"
+"{\n"
+" switch (type) {\n"
+" case 0:\n"
+" float scale = gl_FragCoord.y / height;\n"
+" FragColor = meter_bar_colour * vec4(scale, scale, scale, 1);\n"
+" break;\n"
+" case 1:\n"
+" FragColor = texture(texture_sampler, TexCoord);\n"
+" break;\n"
+" case 2:\n"
+" FragColor = meter_line_colour;\n"
+" break;\n"
+" }\n"
+"}\n";
+
+
+enum class FragmentType
+{
+ METER_BARS = 0,
+ LABEL = 1,
+ SCALE_LINE = 2,
+};
+
+
+auto constexpr label_texture_width = 32;
+auto constexpr label_texture_height = 16;
+
+
+GLMeters::GLMeters(wxWindow* parent, FilmViewer* viewer)
+ : _view(
+ parent,
+ boost::bind(&GLMeters::setup_shaders, this),
+ boost::bind(&GLMeters::create_textures, this),
+ boost::bind(&GLMeters::play_pre_draw, this),
+ boost::bind(&GLMeters::draw, this),
+ boost::bind(&GLMeters::play_post_draw, this),
+ fragment_source
+ )
+ , _viewer(viewer)
+{
+
+}
+
+
+void
+GLMeters::setup_shaders()
+{
+ vector<unsigned int> indices;
+ for (auto i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+ auto rectangle = _coords.add_filled_rectangle();
+ _meter_bars.push_back(rectangle);
+ _meter_bars_group.add(rectangle);
+ }
+ for (auto i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+ _channel_labels.push_back(_coords.add_filled_rectangle());
+ }
+ for (auto i = 0U; i < dcpomatic::meter::scales().size(); ++i) {
+ _scale_labels.push_back(_coords.add_filled_rectangle());
+ _scale_lines.push_back(_coords.add_line());
+ }
+
+ _coords.setup();
+
+ _fragment_type.setup(_view.program(), "type");
+ _texture_sampler.setup(_view.program(), "texture_sampler");
+
+ _meter_bar_colour.setup(_view.program(), "meter_bar_colour");
+ _meter_bar_colour.set(0, 1, 0, 1.0f);
+
+ _meter_line_colour.setup(_view.program(), "meter_line_colour");
+ _meter_line_colour.set(0.7, 0.7, 0.7, 1.0f);
+
+ _height.setup(_view.program(), "height");
+
+ glLineWidth(0.5f);
+ check_gl_error("glLineWidth");
+ glEnable(GL_BLEND);
+ check_gl_error("glEnable");
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ check_gl_error("glBlendFunc");
+}
+
+
+void
+GLMeters::get_next_frame()
+{
+ if (auto levels = _viewer->butler()->get_audio_levels()) {
+ _last_time = levels->time;
+ _peaks = levels->peaks;
+ } else {
+ _last_time = boost::none;
+ _peaks.clear();
+ }
+}
+
+
+void
+GLMeters::play_pre_draw()
+{
+ get_next_frame();
+}
+
+
+void
+GLMeters::draw()
+{
+ auto constexpr meter_gap = 2;
+
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ check_gl_error("glClear");
+
+ if (_peaks.empty()) {
+ glFlush();
+ check_gl_error("glFlush");
+ return;
+ }
+
+ using namespace dcpomatic::meter;
+
+ auto const width = _view.size().GetWidth();
+ auto const height = _view.size().GetHeight();
+ auto const meter_height = height - border() * 2 - channel_label_height();
+ auto const channel_width = (width - scale_label_width() - border() * 2) / _peaks.size();
+
+ _height.set(height);
+
+ /* Peak rectangles that we have data for */
+ auto const have_data = _peaks.size();
+ for (auto i = 0U; i < have_data; ++i) {
+ auto const y = db_to_y(meter_height, linear_level_to_db(_peaks[i]));
+ _meter_bars[i].set(
+ _view.size(),
+ border() + scale_label_width() + i * channel_width + meter_gap / 2,
+ y + border(),
+ wxSize{static_cast<int>(channel_width) - meter_gap, meter_height - y}
+ );
+ }
+
+ /* Scale coordinate labels and lines */
+ int index = 0;
+ for (auto const& i: dcpomatic::meter::scales()) {
+ auto const y = db_to_y(meter_height, i.first);
+ _scale_labels[index].set(
+ _view.size(),
+ border() + scale_label_width() - _scale_label_widths[index] - 4,
+ y + border(),
+ wxSize{label_texture_width, label_texture_height}
+ );
+ _scale_lines[index].set(_view.size(), 0, y + border(), width, y + border());
+ ++index;
+ }
+
+ /* Channel labels */
+ for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+ _channel_labels[i].set(
+ _view.size(),
+ border() + scale_label_width() + channel_width * i + (channel_width - _channel_label_widths[i]) / 2,
+ _view.size().GetHeight() - channel_label_height(),
+ wxSize{label_texture_width, label_texture_height}
+ );
+ }
+
+ _coords.set_array_buffer();
+
+ /* Draw the scales first so that the meter bars can overlay them */
+ _fragment_type.set(static_cast<GLint>(FragmentType::LABEL));
+ for (auto i = 0U; i < dcpomatic::meter::scales().size(); ++i) {
+ _scale_label_textures[i].bind();
+ _texture_sampler.set(i);
+ _scale_labels[i].draw();
+ }
+
+ for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+ _channel_label_textures[i].bind();
+ _texture_sampler.set(dcpomatic::meter::scales().size() + i);
+ _channel_labels[i].draw();
+ }
+
+ _fragment_type.set(static_cast<GLint>(FragmentType::SCALE_LINE));
+ for (auto i = 0U; i < dcpomatic::meter::scales().size(); ++i) {
+ _scale_lines[i].draw();
+ }
+
+ _fragment_type.set(static_cast<GLint>(FragmentType::METER_BARS));
+ _meter_bars_group.draw();
+}
+
+
+int
+GLMeters::play_post_draw()
+{
+ auto time_until_next_frame = [this]() -> boost::optional<int> {
+ if (!_last_time) {
+ return {};
+ }
+
+ return dcpomatic::DCPTime(*_last_time - _viewer->position() + _viewer->butler()->time_between_audio_level_measurements()).seconds() * 1000;
+ };
+
+ while (true) {
+ auto const n = time_until_next_frame();
+ if (!n || *n > 5) {
+ break;
+ }
+ get_next_frame();
+ }
+
+ return time_until_next_frame().get_value_or(0);
+}
+
+
+wxWindow*
+GLMeters::get() const
+{
+ return _view.canvas();
+}
+
+
+void
+GLMeters::start()
+{
+ _view.start();
+}
+
+
+void
+GLMeters::stop()
+{
+ _view.stop();
+}
+
+
+void
+GLMeters::create_textures()
+{
+ auto label = [](wxString text) {
+
+ wxDouble width;
+
+ wxBitmap bitmap(label_texture_width, label_texture_height, 24);
+ {
+ wxMemoryDC dc(bitmap);
+ auto gc = wxGraphicsContext::Create(dc);
+ DCPOMATIC_ASSERT(gc);
+
+ gc->SetAntialiasMode(wxANTIALIAS_DEFAULT);
+ gc->SetFont(gc->CreateFont(*wxSMALL_FONT, *wxWHITE));
+
+ dc.SetPen(*wxGREY_PEN);
+ gc->DrawText(text, 0, 0);
+
+ wxDouble height;
+ wxDouble descent;
+ wxDouble leading;
+ gc->GetTextExtent(text, &width, &height, &descent, &leading);
+
+ delete gc;
+ }
+
+ auto image = make_shared<Image>(AV_PIX_FMT_RGB24, dcp::Size{label_texture_width, label_texture_height}, Image::Alignment::COMPACT);
+ auto* p = image->data()[0];
+
+ wxNativePixelData data(bitmap);
+ wxNativePixelData::Iterator row(data);
+
+ for (auto y = 0; y < label_texture_height; ++y) {
+ auto q = row;
+ for (auto x = 0; x < label_texture_width; ++x) {
+ *p++ = q.Red();
+ *p++ = q.Green();
+ *p++ = q.Blue();
+ ++q;
+ }
+
+ row.OffsetY(data, 1);
+ }
+
+ return make_pair(image, width);
+ };
+
+
+ int unit = 0;
+
+ for (auto scale: dcpomatic::meter::scales()) {
+ auto image_and_width = label(scale.second);
+
+ _scale_label_textures.emplace_back(1, unit++);
+ _scale_label_textures.back().set(image_and_width.first);
+ _scale_label_widths.push_back(image_and_width.second);
+ _scale_label_textures.back().bind();
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ check_gl_error("glTexParameteri");
+
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ check_gl_error("glTexParameterf");
+ }
+
+ for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+ auto image_and_width = label(std_to_wx(short_audio_channel_name(i)));
+
+ _channel_label_textures.emplace_back(1, unit++);
+ _channel_label_textures.back().set(image_and_width.first);
+ _channel_label_widths.push_back(image_and_width.second);
+ _channel_label_textures.back().bind();
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ check_gl_error("glTexParameteri");
+
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ check_gl_error("glTexParameterf");
+ }
+}
+
diff --git a/src/wx/gl_meters.h b/src/wx/gl_meters.h
new file mode 100644
index 000000000..c49981e6c
--- /dev/null
+++ b/src/wx/gl_meters.h
@@ -0,0 +1,88 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "gl_util.h"
+#include "gl_view.h"
+#include "lib/dcpomatic_time.h"
+#include <dcp/warnings.h>
+
+
+class FilmViewer;
+
+
+/* See note in gl_view.h */
+#if wxCHECK_VERSION(3,1,0)
+
+
+class GLMeters
+{
+public:
+ GLMeters(wxWindow* parent, FilmViewer* viewer);
+
+ GLMeters(GLMeters const&) = delete;
+ GLMeters& operator=(GLMeters const&) = delete;
+
+ wxWindow* get() const;
+
+ void start();
+ void stop();
+
+private:
+ void play_pre_draw();
+ void draw();
+ int play_post_draw();
+
+ void get_next_frame();
+ void setup_shaders();
+ void create_textures();
+
+ GLView _view;
+ FilmViewer* _viewer;
+
+ boost::optional<dcpomatic::DCPTime> _last_time;
+ std::vector<float> _peaks;
+
+ dcpomatic::gl::Uniform1i _fragment_type;
+ dcpomatic::gl::Uniform1i _texture_sampler;
+ dcpomatic::gl::UniformVec4f _meter_bar_colour;
+ dcpomatic::gl::UniformVec4f _meter_line_colour;
+ dcpomatic::gl::Uniform1i _height;
+
+ /** Number of rectangles we need nodes and coordinates for */
+ int _rectangles = 0;
+ /** Number of lines we need nodes and coordinates for */
+ int _lines = 0;
+
+ std::vector<dcpomatic::gl::Texture> _scale_label_textures;
+ std::vector<int> _scale_label_widths;
+ std::vector<dcpomatic::gl::Texture> _channel_label_textures;
+ std::vector<int> _channel_label_widths;
+
+ dcpomatic::gl::Coords _coords;
+ std::vector<dcpomatic::gl::Coords::FilledRectangle> _meter_bars;
+ dcpomatic::gl::Coords::FilledRectangleGroup _meter_bars_group;
+ std::vector<dcpomatic::gl::Coords::FilledRectangle> _channel_labels;
+ std::vector<dcpomatic::gl::Coords::FilledRectangle> _scale_labels;
+ std::vector<dcpomatic::gl::Coords::Line> _scale_lines;
+};
+
+
+#endif
diff --git a/src/wx/gl_util.cc b/src/wx/gl_util.cc
index 8c09acd0e..6a78f4142 100644
--- a/src/wx/gl_util.cc
+++ b/src/wx/gl_util.cc
@@ -358,6 +358,64 @@ Coords::FilledRectangleGroup::draw()
}
+Coords::Line::Line(Coords& coords)
+ : _coords(coords)
+ , _node(coords._nodes)
+ , _index(coords._indices.size())
+{
+ auto& indices = coords._indices;
+ auto& nodes = coords._nodes;
+
+ indices.push_back(nodes + 0);
+ indices.push_back(nodes + 1);
+ nodes += 2;
+
+ auto& c = _coords._coords;
+
+ auto offset = c.size();
+
+ c.resize(c.size() + 5 * 2);
+
+ c[offset + 2] = 0.0f;
+ c[offset + 3] = 1.0f;
+ c[offset + 4] = 1.0f;
+
+ c[offset + 7] = 0.0f;
+ c[offset + 8] = 1.0f;
+ c[offset + 9] = 0.0f;
+}
+
+
+void
+Coords::Line::set(wxSize canvas_size, int x1, int y1, int x2, int y2)
+{
+ auto& coords = _coords._coords;
+
+ auto const o = _node * 5;
+
+ coords[o] = x_pixels_to_gl(canvas_size, x1);
+ coords[o + 1] = y_pixels_to_gl(canvas_size, y1);
+
+ coords[o + 5] = x_pixels_to_gl(canvas_size, x2);
+ coords[o + 6] = y_pixels_to_gl(canvas_size, y2);
+}
+
+
+void
+Coords::Line::draw()
+{
+ glDrawElements(GL_LINES, 2, GL_UNSIGNED_INT, reinterpret_cast<void*>(_index * sizeof(int)));
+}
+
+
+Coords::Line
+Coords::add_line()
+{
+ DCPOMATIC_ASSERT(!_setup);
+ return Line(*this);
+}
+
+
void
Coords::setup()
{
diff --git a/src/wx/gl_util.h b/src/wx/gl_util.h
index 101a35721..f5baa04c7 100644
--- a/src/wx/gl_util.h
+++ b/src/wx/gl_util.h
@@ -23,6 +23,7 @@
#define DCPOMATIC_GL_UTIL_H
+#include "lib/dcpomatic_assert.h"
#include <dcp/types.h>
#include <wx/wx.h>
@@ -119,6 +120,20 @@ public:
void draw() override;
};
+ class Line
+ {
+ public:
+ Line(Coords& coords);
+
+ void set(wxSize canvas_size, int x1, int y1, int x2, int y2);
+ void draw();
+
+ private:
+ Coords& _coords;
+ int _node;
+ int _index;
+ };
+
class FilledRectangleGroup
{
public:
@@ -132,6 +147,7 @@ public:
FilledRectangle add_filled_rectangle();
OutlineRectangle add_outline_rectangle();
+ Line add_line();
void setup();
void set_array_buffer();
diff --git a/src/wx/gl_video_view.h b/src/wx/gl_video_view.h
index dddcbff48..82450293a 100644
--- a/src/wx/gl_video_view.h
+++ b/src/wx/gl_video_view.h
@@ -19,6 +19,7 @@
*/
+#include "gl_meters.h"
#include "gl_view.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
@@ -57,6 +58,10 @@ public:
void start() override;
void stop() override;
+ wxWindow* get_meters() const override {
+ return _meters.get();
+ }
+
NextFrameResult display_next_frame(bool) override;
bool vsync_enabled() const {
@@ -124,6 +129,8 @@ private:
std::shared_ptr<wxTimer> _timer;
std::map<GLenum, std::string> _information;
+
+ GLMeters _meters;
};
#endif
diff --git a/src/wx/simple_video_view.h b/src/wx/simple_video_view.h
index 2c6ce7965..c701c48e5 100644
--- a/src/wx/simple_video_view.h
+++ b/src/wx/simple_video_view.h
@@ -44,7 +44,7 @@ public:
return _panel;
}
- wxWindow* get_meters() {
+ wxWindow* get_meters() const override {
return _meters.get();
}
diff --git a/src/wx/video_view.h b/src/wx/video_view.h
index 3ea03a5fd..a7ab36388 100644
--- a/src/wx/video_view.h
+++ b/src/wx/video_view.h
@@ -63,6 +63,8 @@ public:
/** Called when playback stops */
virtual void stop () {}
+ virtual wxWindow* get_meters() const = 0;
+
enum NextFrameResult {
FAIL,
AGAIN,
diff --git a/src/wx/wscript b/src/wx/wscript
index f1066dabe..9e805c752 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -96,6 +96,7 @@ sources = """
gain_calculator_dialog.cc
gdc_certificate_panel.cc
general_preferences_page.cc
+ gl_meters.cc
gl_view.cc
gl_video_view.cc
gl_util.cc