summaryrefslogtreecommitdiff
path: root/src/wx
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2021-09-27 13:43:19 +0200
committerCarl Hetherington <cth@carlh.net>2021-09-27 13:43:19 +0200
commit952084c4221c5708e02c783284cf0f7239c6b4c4 (patch)
treec1521dab0586b6c33e02c9338c94abdc6b4c2ea9 /src/wx
parent6e3e984162ca7a181bc7c98d90c295e88e4e7f6c (diff)
parent81b9ea804cb9953bb1a4074f208f63374adbf09b (diff)
Merge branch 'better-gl' into v2.15.x
This changes the GL video view to use more modern GL (GLSL etc.) It also special-cases JPEG2000 video playback and does scaling and colourspace conversion on the GPU.
Diffstat (limited to 'src/wx')
-rw-r--r--src/wx/controls.cc109
-rw-r--r--src/wx/controls.h2
-rw-r--r--src/wx/film_viewer.cc43
-rw-r--r--src/wx/film_viewer.h16
-rw-r--r--src/wx/gl_video_view.cc635
-rw-r--r--src/wx/gl_video_view.h78
-rw-r--r--src/wx/playlist_controls.cc37
-rw-r--r--src/wx/simple_video_view.cc34
-rw-r--r--src/wx/simple_video_view.h12
-rw-r--r--src/wx/standard_controls.cc7
-rw-r--r--src/wx/system_information_dialog.cc41
-rw-r--r--src/wx/system_information_dialog.h4
-rw-r--r--src/wx/video_view.cc1
-rw-r--r--src/wx/video_view.h6
-rw-r--r--src/wx/video_waveform_plot.cc4
-rw-r--r--src/wx/wscript6
16 files changed, 791 insertions, 244 deletions
diff --git a/src/wx/controls.cc b/src/wx/controls.cc
index 700769a50..29f4aeaa3 100644
--- a/src/wx/controls.cc
+++ b/src/wx/controls.cc
@@ -144,13 +144,13 @@ Controls::Controls (wxWindow* parent, shared_ptr<FilmViewer> viewer, bool editor
_jump_to_selected->SetValue (Config::instance()->jump_to_selected ());
}
- _viewer->Started.connect (boost::bind(&Controls::started, this));
- _viewer->Stopped.connect (boost::bind(&Controls::stopped, this));
+ viewer->Started.connect (boost::bind(&Controls::started, this));
+ viewer->Stopped.connect (boost::bind(&Controls::stopped, this));
Bind (wxEVT_TIMER, boost::bind(&Controls::update_position, this));
_timer.Start (80, wxTIMER_CONTINUOUS);
- set_film (_viewer->film());
+ set_film (viewer->film());
setup_sensitivity ();
@@ -186,7 +186,12 @@ Controls::stopped ()
void
Controls::update_position ()
{
- if (!_slider_being_moved && !_viewer->pending_idle_get()) {
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
+ if (!_slider_being_moved && !viewer->pending_idle_get()) {
update_position_label ();
update_position_slider ();
}
@@ -196,14 +201,24 @@ Controls::update_position ()
void
Controls::eye_changed ()
{
- _viewer->set_eyes (_eye->GetSelection() == 0 ? Eyes::LEFT : Eyes::RIGHT);
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
+ viewer->set_eyes (_eye->GetSelection() == 0 ? Eyes::LEFT : Eyes::RIGHT);
}
void
Controls::outline_content_changed ()
{
- _viewer->set_outline_content (_outline_content->GetValue());
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
+ viewer->set_outline_content (_outline_content->GetValue());
}
@@ -211,13 +226,14 @@ Controls::outline_content_changed ()
void
Controls::slider_moved (bool page)
{
- if (!_film) {
+ auto viewer = _viewer.lock ();
+ if (!_film || !viewer) {
return;
}
if (!page && !_slider_being_moved) {
/* This is the first event of a drag; stop playback for the duration of the drag */
- _viewer->suspend ();
+ viewer->suspend ();
_slider_being_moved = true;
}
@@ -228,10 +244,10 @@ Controls::slider_moved (bool page)
*/
bool accurate = false;
if (t >= _film->length ()) {
- t = _film->length() - _viewer->one_video_frame();
+ t = _film->length() - viewer->one_video_frame();
accurate = true;
}
- _viewer->seek (t, accurate);
+ viewer->seek (t, accurate);
update_position_label ();
log (
@@ -245,8 +261,13 @@ Controls::slider_moved (bool page)
void
Controls::slider_released ()
{
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
/* Restart after a drag */
- _viewer->resume ();
+ viewer->resume ();
_slider_being_moved = false;
}
@@ -259,10 +280,15 @@ Controls::update_position_slider ()
return;
}
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
auto const len = _film->length ();
if (len.get ()) {
- int const new_slider_position = 4096 * _viewer->position().get() / len.get();
+ int const new_slider_position = 4096 * viewer->position().get() / len.get();
if (new_slider_position != _slider->GetValue()) {
_slider->SetValue (new_slider_position);
}
@@ -279,10 +305,15 @@ Controls::update_position_label ()
return;
}
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
double const fps = _film->video_frame_rate ();
/* Count frame number from 1 ... not sure if this is the best idea */
- checked_set (_frame_number, wxString::Format (wxT("%ld"), lrint (_viewer->position().seconds() * fps) + 1));
- checked_set (_timecode, time_to_timecode (_viewer->position(), fps));
+ checked_set (_frame_number, wxString::Format (wxT("%ld"), lrint (viewer->position().seconds() * fps) + 1));
+ checked_set (_timecode, time_to_timecode (viewer->position(), fps));
}
@@ -297,7 +328,12 @@ Controls::active_jobs_changed (optional<string> j)
DCPTime
Controls::nudge_amount (wxKeyboardState& ev)
{
- auto amount = _viewer->one_video_frame ();
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return {};
+ }
+
+ auto amount = viewer->one_video_frame ();
if (ev.ShiftDown() && !ev.ControlDown()) {
amount = DCPTime::from_seconds (1);
@@ -314,7 +350,10 @@ Controls::nudge_amount (wxKeyboardState& ev)
void
Controls::rewind_clicked (wxMouseEvent& ev)
{
- _viewer->seek (DCPTime(), true);
+ auto viewer = _viewer.lock ();
+ if (viewer) {
+ viewer->seek (DCPTime(), true);
+ }
ev.Skip();
}
@@ -322,28 +361,40 @@ Controls::rewind_clicked (wxMouseEvent& ev)
void
Controls::back_frame ()
{
- _viewer->seek_by (-_viewer->one_video_frame(), true);
+ auto viewer = _viewer.lock ();
+ if (viewer) {
+ viewer->seek_by (-viewer->one_video_frame(), true);
+ }
}
void
Controls::forward_frame ()
{
- _viewer->seek_by (_viewer->one_video_frame(), true);
+ auto viewer = _viewer.lock ();
+ if (viewer) {
+ viewer->seek_by (viewer->one_video_frame(), true);
+ }
}
void
Controls::back_clicked (wxKeyboardState& ev)
{
- _viewer->seek_by (-nudge_amount(ev), true);
+ auto viewer = _viewer.lock ();
+ if (viewer) {
+ viewer->seek_by (-nudge_amount(ev), true);
+ }
}
void
Controls::forward_clicked (wxKeyboardState& ev)
{
- _viewer->seek_by (nudge_amount(ev), true);
+ auto viewer = _viewer.lock ();
+ if (viewer) {
+ viewer->seek_by (nudge_amount(ev), true);
+ }
}
@@ -376,9 +427,14 @@ Controls::setup_sensitivity ()
void
Controls::timecode_clicked ()
{
- auto dialog = new PlayheadToTimecodeDialog (this, _viewer->position(), _film->video_frame_rate());
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
+ auto dialog = new PlayheadToTimecodeDialog (this, viewer->position(), _film->video_frame_rate());
if (dialog->ShowModal() == wxID_OK) {
- _viewer->seek (dialog->get(), true);
+ viewer->seek (dialog->get(), true);
}
dialog->Destroy ();
}
@@ -387,9 +443,14 @@ Controls::timecode_clicked ()
void
Controls::frame_number_clicked ()
{
- auto dialog = new PlayheadToFrameDialog (this, _viewer->position(), _film->video_frame_rate());
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
+ auto dialog = new PlayheadToFrameDialog (this, viewer->position(), _film->video_frame_rate());
if (dialog->ShowModal() == wxID_OK) {
- _viewer->seek (dialog->get(), true);
+ viewer->seek (dialog->get(), true);
}
dialog->Destroy ();
}
diff --git a/src/wx/controls.h b/src/wx/controls.h
index 9a22d7709..377960425 100644
--- a/src/wx/controls.h
+++ b/src/wx/controls.h
@@ -79,7 +79,7 @@ protected:
wxBoxSizer* _button_sizer;
std::shared_ptr<Film> _film;
wxSlider* _slider;
- std::shared_ptr<FilmViewer> _viewer;
+ std::weak_ptr<FilmViewer> _viewer;
boost::optional<std::string> _active_job;
private:
diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc
index c7e154fa5..0131aa294 100644
--- a/src/wx/film_viewer.cc
+++ b/src/wx/film_viewer.cc
@@ -63,17 +63,11 @@ extern "C" {
using std::bad_alloc;
using std::cout;
using std::dynamic_pointer_cast;
-using std::exception;
-using std::list;
-using std::make_pair;
using std::make_shared;
using std::max;
-using std::min;
-using std::pair;
using std::shared_ptr;
using std::string;
using std::vector;
-using std::weak_ptr;
using boost::optional;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
@@ -96,10 +90,10 @@ FilmViewer::FilmViewer (wxWindow* p)
{
switch (Config::instance()->video_view_type()) {
case Config::VIDEO_VIEW_OPENGL:
- _video_view = new GLVideoView (this, p);
+ _video_view = std::make_shared<GLVideoView>(this, p);
break;
case Config::VIDEO_VIEW_SIMPLE:
- _video_view = new SimpleVideoView (this, p);
+ _video_view = std::make_shared<SimpleVideoView>(this, p);
break;
}
@@ -169,7 +163,7 @@ FilmViewer::set_film (shared_ptr<Film> film)
}
try {
- _player = make_shared<Player>(_film);
+ _player = make_shared<Player>(_film, _optimise_for_j2k ? Image::Alignment::COMPACT : Image::Alignment::PADDED);
_player->set_fast ();
if (_dcp_decode_reduction) {
_player->set_dcp_decode_reduction (_dcp_decode_reduction);
@@ -214,6 +208,8 @@ FilmViewer::recreate_butler ()
return;
}
+ auto const j2k_gl_optimised = dynamic_pointer_cast<GLVideoView>(_video_view) && _optimise_for_j2k;
+
_butler = std::make_shared<Butler>(
_film,
_player,
@@ -221,8 +217,9 @@ FilmViewer::recreate_butler ()
_audio_channels,
bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24),
VideoRange::FULL,
- false,
- true
+ j2k_gl_optimised ? Image::Alignment::COMPACT : Image::Alignment::PADDED,
+ true,
+ j2k_gl_optimised
);
if (!Config::instance()->sound() && !_audio.isStreamOpen()) {
@@ -281,21 +278,22 @@ FilmViewer::calculate_sizes ()
auto const view_ratio = float(_video_view->get()->GetSize().x) / _video_view->get()->GetSize().y;
auto const film_ratio = container ? container->ratio () : 1.78;
+ dcp::Size out_size;
if (view_ratio < film_ratio) {
/* panel is less widscreen than the film; clamp width */
- _out_size.width = _video_view->get()->GetSize().x;
- _out_size.height = lrintf (_out_size.width / film_ratio);
+ out_size.width = _video_view->get()->GetSize().x;
+ out_size.height = lrintf (out_size.width / film_ratio);
} else {
/* panel is more widescreen than the film; clamp height */
- _out_size.height = _video_view->get()->GetSize().y;
- _out_size.width = lrintf (_out_size.height * film_ratio);
+ out_size.height = _video_view->get()->GetSize().y;
+ out_size.width = lrintf (out_size.height * film_ratio);
}
/* Catch silly values */
- _out_size.width = max (64, _out_size.width);
- _out_size.height = max (64, _out_size.height);
+ out_size.width = max (64, out_size.width);
+ out_size.height = max (64, out_size.height);
- _player->set_video_container_size (_out_size);
+ _player->set_video_container_size (out_size);
}
@@ -771,3 +769,12 @@ FilmViewer::image_changed (shared_ptr<PlayerVideo> pv)
{
emit (boost::bind(boost::ref(ImageChanged), pv));
}
+
+
+void
+FilmViewer::set_optimise_for_j2k (bool o)
+{
+ _optimise_for_j2k = o;
+ _video_view->set_optimise_for_j2k (o);
+}
+
diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h
index 2efe448c9..5e5bb7916 100644
--- a/src/wx/film_viewer.h
+++ b/src/wx/film_viewer.h
@@ -62,7 +62,7 @@ public:
return _video_view->get();
}
- VideoView const * video_view () const {
+ std::shared_ptr<const VideoView> video_view () const {
return _video_view;
}
@@ -98,6 +98,7 @@ public:
void set_outline_subtitles (boost::optional<dcpomatic::Rect<double>>);
void set_eyes (Eyes e);
void set_pad_black (bool p);
+ void set_optimise_for_j2k (bool o);
void slow_refresh ();
@@ -115,9 +116,6 @@ public:
}
/* Some accessors and utility methods that VideoView classes need */
- dcp::Size out_size () const {
- return _out_size;
- }
bool outline_content () const {
return _outline_content;
}
@@ -172,13 +170,10 @@ private:
std::shared_ptr<Film> _film;
std::shared_ptr<Player> _player;
- VideoView* _video_view = nullptr;
+ std::shared_ptr<VideoView> _video_view;
bool _coalesce_player_changes = false;
std::vector<int> _pending_player_changes;
- /** Size of our output (including padding if we have any) */
- dcp::Size _out_size;
-
RtAudio _audio;
int _audio_channels = 0;
unsigned int _audio_block_size = 1024;
@@ -193,6 +188,11 @@ private:
boost::optional<int> _dcp_decode_reduction;
+ /** true to assume that this viewer is only being used for JPEG2000 sources
+ * so it can optimise accordingly.
+ */
+ bool _optimise_for_j2k = false;
+
ClosedCaptionsDialog* _closed_captions_dialog = nullptr;
bool _outline_content = false;
diff --git a/src/wx/gl_video_view.cc b/src/wx/gl_video_view.cc
index 7bf9e3adc..04e0bac46 100644
--- a/src/wx/gl_video_view.cc
+++ b/src/wx/gl_video_view.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2018-2019 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -18,6 +18,11 @@
*/
+
+#ifdef DCPOMATIC_WINDOWS
+#include <GL/glew.h>
+#endif
+
#include "gl_video_view.h"
#include "film_viewer.h"
#include "wx_util.h"
@@ -31,10 +36,9 @@
#include <iostream>
#ifdef DCPOMATIC_OSX
-#include <OpenGL/glu.h>
-#include <OpenGL/glext.h>
-#include <OpenGL/CGLTypes.h>
+#define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED
#include <OpenGL/OpenGL.h>
+#include <OpenGL/gl3.h>
#endif
#ifdef DCPOMATIC_LINUX
@@ -44,12 +48,13 @@
#ifdef DCPOMATIC_WINDOWS
#include <GL/glu.h>
-#include <GL/glext.h>
#include <GL/wglext.h>
#endif
+
using std::cout;
using std::shared_ptr;
+using std::string;
using boost::optional;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
@@ -69,12 +74,19 @@ check_gl_error (char const * last)
GLVideoView::GLVideoView (FilmViewer* viewer, wxWindow *parent)
: VideoView (viewer)
, _context (nullptr)
- , _have_storage (false)
, _vsync_enabled (false)
, _playing (false)
, _one_shot (false)
{
- _canvas = new wxGLCanvas (parent, wxID_ANY, 0, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE);
+ wxGLAttributes attributes;
+ /* We don't need a depth buffer, and indeed there is apparently a bug with Windows/Intel HD 630
+ * which puts green lines over the OpenGL display if you have a non-zero depth buffer size.
+ * https://community.intel.com/t5/Graphics/Request-for-details-on-Intel-HD-630-green-lines-in-OpenGL-apps/m-p/1202179
+ */
+ attributes.PlatformDefaults().MinRGBA(8, 8, 8, 8).DoubleBuffer().Depth(0).EndList();
+ _canvas = new wxGLCanvas (
+ parent, attributes, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE
+ );
_canvas->Bind (wxEVT_PAINT, boost::bind(&GLVideoView::update, this));
_canvas->Bind (wxEVT_SIZE, boost::bind(&GLVideoView::size_changed, this, _1));
@@ -101,8 +113,6 @@ GLVideoView::~GLVideoView ()
_thread.interrupt ();
_thread.join ();
} catch (...) {}
-
- glDeleteTextures (1, &_id);
}
void
@@ -130,9 +140,18 @@ GLVideoView::update ()
return;
}
+ /* It appears important to do this from the GUI thread; if we do it from the GL thread
+ * on Linux we get strange failures to create the context for any version of GL higher
+ * than 3.2.
+ */
+ ensure_context ();
+
#ifdef DCPOMATIC_OSX
/* macOS gives errors if we don't do this (and therefore [NSOpenGLContext setView:]) from the main thread */
- ensure_context ();
+ if (!_setup_shaders_done) {
+ setup_shaders ();
+ _setup_shaders_done = true;
+ }
#endif
if (!_thread.joinable()) {
@@ -140,34 +159,307 @@ GLVideoView::update ()
}
request_one_shot ();
+
+ rethrow ();
}
+static constexpr char vertex_source[] =
+"#version 330 core\n"
+"\n"
+"layout (location = 0) in vec3 in_pos;\n"
+"layout (location = 1) in vec2 in_tex_coord;\n"
+"\n"
+"out vec2 TexCoord;\n"
+"\n"
+"void main()\n"
+"{\n"
+" gl_Position = vec4(in_pos, 1.0);\n"
+" TexCoord = in_tex_coord;\n"
+"}\n";
+
+
+/* Bicubic interpolation stolen from https://stackoverflow.com/questions/13501081/efficient-bicubic-filtering-code-in-glsl */
+static constexpr char fragment_source[] =
+"#version 330 core\n"
+"\n"
+"in vec2 TexCoord;\n"
+"\n"
+"uniform sampler2D texture_sampler;\n"
+/* type = 0: draw border
+ * type = 1: draw XYZ image
+ * type = 2: draw RGB image
+ */
+"uniform int type = 0;\n"
+"uniform vec4 border_colour;\n"
+"uniform mat4 colour_conversion;\n"
+"\n"
+"out vec4 FragColor;\n"
+"\n"
+"vec4 cubic(float x)\n"
+"\n"
+"#define IN_GAMMA 2.2\n"
+"#define OUT_GAMMA 0.384615385\n" // 1 / 2.6
+"#define DCI_COEFFICIENT 0.91655528\n" // 48 / 53.37
+"\n"
+"{\n"
+" float x2 = x * x;\n"
+" float x3 = x2 * x;\n"
+" vec4 w;\n"
+" w.x = -x3 + 3 * x2 - 3 * x + 1;\n"
+" w.y = 3 * x3 - 6 * x2 + 4;\n"
+" w.z = -3 * x3 + 3 * x2 + 3 * x + 1;\n"
+" w.w = x3;\n"
+" return w / 6.f;\n"
+"}\n"
+"\n"
+"vec4 texture_bicubic(sampler2D sampler, vec2 tex_coords)\n"
+"{\n"
+" vec2 tex_size = textureSize(sampler, 0);\n"
+" vec2 inv_tex_size = 1.0 / tex_size;\n"
+"\n"
+" tex_coords = tex_coords * tex_size - 0.5;\n"
+"\n"
+" vec2 fxy = fract(tex_coords);\n"
+" tex_coords -= fxy;\n"
+"\n"
+" vec4 xcubic = cubic(fxy.x);\n"
+" vec4 ycubic = cubic(fxy.y);\n"
+"\n"
+" vec4 c = tex_coords.xxyy + vec2 (-0.5, +1.5).xyxy;\n"
+"\n"
+" vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);\n"
+" vec4 offset = c + vec4 (xcubic.yw, ycubic.yw) / s;\n"
+"\n"
+" offset *= inv_tex_size.xxyy;\n"
+"\n"
+" vec4 sample0 = texture(sampler, offset.xz);\n"
+" vec4 sample1 = texture(sampler, offset.yz);\n"
+" vec4 sample2 = texture(sampler, offset.xw);\n"
+" vec4 sample3 = texture(sampler, offset.yw);\n"
+"\n"
+" float sx = s.x / (s.x + s.y);\n"
+" float sy = s.z / (s.z + s.w);\n"
+"\n"
+" return mix(\n"
+" mix(sample3, sample2, sx), mix(sample1, sample0, sx)\n"
+" , sy);\n"
+"}\n"
+"\n"
+"void main()\n"
+"{\n"
+" switch (type) {\n"
+" case 0:\n"
+" FragColor = border_colour;\n"
+" break;\n"
+" case 1:\n"
+" FragColor = texture_bicubic(texture_sampler, TexCoord);\n"
+" FragColor.x = pow(FragColor.x, IN_GAMMA) / DCI_COEFFICIENT;\n"
+" FragColor.y = pow(FragColor.y, IN_GAMMA) / DCI_COEFFICIENT;\n"
+" FragColor.z = pow(FragColor.z, IN_GAMMA) / DCI_COEFFICIENT;\n"
+" FragColor = colour_conversion * FragColor;\n"
+" FragColor.x = pow(FragColor.x, OUT_GAMMA);\n"
+" FragColor.y = pow(FragColor.y, OUT_GAMMA);\n"
+" FragColor.z = pow(FragColor.z, OUT_GAMMA);\n"
+" break;\n"
+" case 2:\n"
+" FragColor = texture_bicubic(texture_sampler, TexCoord);\n"
+" break;\n"
+" }\n"
+"}\n";
+
+
void
GLVideoView::ensure_context ()
{
if (!_context) {
- _context = new wxGLContext (_canvas);
- _canvas->SetCurrent (*_context);
+ wxGLContextAttrs attrs;
+ attrs.PlatformDefaults().CoreProfile().OGLVersion(4, 1).EndList();
+ _context = new wxGLContext (_canvas, nullptr, &attrs);
+ if (!_context->IsOK()) {
+ throw GLError ("Making GL context", -1);
+ }
}
}
+
+/* Offset of video texture triangles in indices */
+static constexpr int indices_video_texture = 0;
+/* Offset of subtitle texture triangles in indices */
+static constexpr int indices_subtitle_texture = 6;
+/* Offset of border lines in indices */
+static constexpr int indices_border = 12;
+
+static constexpr unsigned int indices[] = {
+ 0, 1, 3, // video texture triangle #1
+ 1, 2, 3, // video texture triangle #2
+ 4, 5, 7, // subtitle texture triangle #1
+ 5, 6, 7, // subtitle texture triangle #2
+ 8, 9, // border line #1
+ 9, 10, // border line #2
+ 10, 11, // border line #3
+ 11, 8, // border line #4
+};
+
+
void
-GLVideoView::draw (Position<int> inter_position, dcp::Size inter_size)
+GLVideoView::setup_shaders ()
{
- glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- check_gl_error ("glClear");
+ DCPOMATIC_ASSERT (_canvas);
+ DCPOMATIC_ASSERT (_context);
+ auto r = _canvas->SetCurrent (*_context);
+ DCPOMATIC_ASSERT (r);
+
+#ifdef DCPOMATIC_WINDOWS
+ r = glewInit();
+ if (r != GLEW_OK) {
+ throw GLError(reinterpret_cast<char const*>(glewGetErrorString(r)));
+ }
+#endif
- glClearColor (0.0f, 0.0f, 0.0f, 1.0f);
- check_gl_error ("glClearColor");
- glEnable (GL_TEXTURE_2D);
- check_gl_error ("glEnable GL_TEXTURE_2D");
+ auto get_information = [this](GLenum name) {
+ auto s = glGetString (name);
+ if (s) {
+ _information[name] = std::string (reinterpret_cast<char const *>(s));
+ }
+ };
+
+ get_information (GL_VENDOR);
+ get_information (GL_RENDERER);
+ get_information (GL_VERSION);
+ get_information (GL_SHADING_LANGUAGE_VERSION);
+
+ glGenVertexArrays(1, &_vao);
+ check_gl_error ("glGenVertexArrays");
+ GLuint vbo;
+ glGenBuffers(1, &vbo);
+ check_gl_error ("glGenBuffers");
+ GLuint ebo;
+ glGenBuffers(1, &ebo);
+ check_gl_error ("glGenBuffers");
+
+ glBindVertexArray(_vao);
+ check_gl_error ("glBindVertexArray");
+
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ check_gl_error ("glBindBuffer");
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+ check_gl_error ("glBindBuffer");
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+ check_gl_error ("glBufferData");
+
+ /* position attribute to vertex shader (location = 0) */
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), nullptr);
+ glEnableVertexAttribArray(0);
+ /* texture coord attribute to vertex shader (location = 1) */
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), reinterpret_cast<void*>(3 * sizeof(float)));
+ glEnableVertexAttribArray(1);
+ check_gl_error ("glEnableVertexAttribArray");
+
+ auto compile = [](GLenum type, char const* source) -> GLuint {
+ auto shader = glCreateShader(type);
+ DCPOMATIC_ASSERT (shader);
+ GLchar const * src[] = { static_cast<GLchar const *>(source) };
+ glShaderSource(shader, 1, src, nullptr);
+ check_gl_error ("glShaderSource");
+ glCompileShader(shader);
+ check_gl_error ("glCompileShader");
+ GLint ok;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
+ if (!ok) {
+ GLint log_length;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+ string log;
+ if (log_length > 0) {
+ char* log_char = new char[log_length];
+ glGetShaderInfoLog(shader, log_length, nullptr, log_char);
+ log = string(log_char);
+ delete[] log_char;
+ }
+ glDeleteShader(shader);
+ throw GLError(String::compose("Could not compile shader (%1)", log).c_str(), -1);
+ }
+ return shader;
+ };
+
+ auto vertex_shader = compile (GL_VERTEX_SHADER, vertex_source);
+ auto fragment_shader = compile (GL_FRAGMENT_SHADER, fragment_source);
+
+ auto program = glCreateProgram();
+ check_gl_error ("glCreateProgram");
+ glAttachShader (program, vertex_shader);
+ check_gl_error ("glAttachShader");
+ glAttachShader (program, fragment_shader);
+ check_gl_error ("glAttachShader");
+ glLinkProgram (program);
+ check_gl_error ("glLinkProgram");
+ GLint ok;
+ glGetProgramiv (program, GL_LINK_STATUS, &ok);
+ if (!ok) {
+ GLint log_length;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+ string log;
+ if (log_length > 0) {
+ char* log_char = new char[log_length];
+ glGetProgramInfoLog(program, log_length, nullptr, log_char);
+ log = string(log_char);
+ delete[] log_char;
+ }
+ glDeleteProgram (program);
+ throw GLError(String::compose("Could not link shader (%1)", log).c_str(), -1);
+ }
+ glDeleteShader (vertex_shader);
+ glDeleteShader (fragment_shader);
+
+ glUseProgram (program);
+
+ _fragment_type = glGetUniformLocation (program, "type");
+ check_gl_error ("glGetUniformLocation");
+ set_border_colour (program);
+
+ auto conversion = dcp::ColourConversion::rec709_to_xyz();
+ boost::numeric::ublas::matrix<double> matrix = conversion.xyz_to_rgb ();
+ GLfloat gl_matrix[] = {
+ static_cast<float>(matrix(0, 0)), static_cast<float>(matrix(0, 1)), static_cast<float>(matrix(0, 2)), 0.0f,
+ static_cast<float>(matrix(1, 0)), static_cast<float>(matrix(1, 1)), static_cast<float>(matrix(1, 2)), 0.0f,
+ static_cast<float>(matrix(2, 0)), static_cast<float>(matrix(2, 1)), static_cast<float>(matrix(2, 2)), 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+
+ auto colour_conversion = glGetUniformLocation (program, "colour_conversion");
+ check_gl_error ("glGetUniformLocation");
+ glUniformMatrix4fv (colour_conversion, 1, GL_TRUE, gl_matrix);
+
+ glLineWidth (2.0f);
glEnable (GL_BLEND);
- check_gl_error ("glEnable GL_BLEND");
- glDisable (GL_DEPTH_TEST);
- check_gl_error ("glDisable GL_DEPTH_TEST");
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ /* Reserve space for the GL_ARRAY_BUFFER */
+ glBufferData(GL_ARRAY_BUFFER, 12 * 5 * sizeof(float), nullptr, GL_STATIC_DRAW);
+ check_gl_error ("glBufferData");
+}
+
+
+void
+GLVideoView::set_border_colour (GLuint program)
+{
+ auto uniform = glGetUniformLocation (program, "border_colour");
+ check_gl_error ("glGetUniformLocation");
+ auto colour = outline_content_colour ();
+ glUniform4f (uniform, colour.Red() / 255.0f, colour.Green() / 255.0f, colour.Blue() / 255.0f, 1.0f);
+ check_gl_error ("glUniform4f");
+}
+
+
+void
+GLVideoView::draw (Position<int>, dcp::Size)
+{
+ auto pad = pad_colour();
+ glClearColor(pad.Red() / 255.0, pad.Green() / 255.0, pad.Blue() / 255.0, 1.0);
+ glClear (GL_COLOR_BUFFER_BIT);
+ check_gl_error ("glClear");
+
auto const size = _canvas_size.load();
int const width = size.GetWidth();
int const height = size.GetHeight();
@@ -178,83 +470,21 @@ GLVideoView::draw (Position<int> inter_position, dcp::Size inter_size)
glViewport (0, 0, width, height);
check_gl_error ("glViewport");
- glMatrixMode (GL_PROJECTION);
- glLoadIdentity ();
-
-DCPOMATIC_DISABLE_WARNINGS
- gluOrtho2D (0, width, height, 0);
-DCPOMATIC_ENABLE_WARNINGS
- check_gl_error ("gluOrtho2d");
- glMatrixMode (GL_MODELVIEW);
- glLoadIdentity ();
-
- glTranslatef (0, 0, 0);
-
- dcp::Size const out_size = _viewer->out_size ();
-
- if (_size) {
- /* Render our image (texture) */
- glBegin (GL_QUADS);
- glTexCoord2f (0, 1);
- glVertex2f (0, _size->height);
- glTexCoord2f (1, 1);
- glVertex2f (_size->width, _size->height);
- glTexCoord2f (1, 0);
- glVertex2f (_size->width, 0);
- glTexCoord2f (0, 0);
- glVertex2f (0, 0);
- glEnd ();
- } else {
- /* No image, so just fill with black */
- glBegin (GL_QUADS);
- glColor3ub (0, 0, 0);
- glVertex2f (0, 0);
- glVertex2f (out_size.width, 0);
- glVertex2f (out_size.width, out_size.height);
- glVertex2f (0, out_size.height);
- glVertex2f (0, 0);
- glEnd ();
- }
- if (!_viewer->pad_black() && out_size.width < width) {
- glBegin (GL_QUADS);
- /* XXX: these colours are right for GNOME; may need adjusting for other OS */
- glColor3ub (240, 240, 240);
- glVertex2f (out_size.width, 0);
- glVertex2f (width, 0);
- glVertex2f (width, height);
- glVertex2f (out_size.width, height);
- glEnd ();
- glColor3ub (255, 255, 255);
+ glBindVertexArray(_vao);
+ check_gl_error ("glBindVertexArray");
+ glUniform1i(_fragment_type, _optimise_for_j2k ? 1 : 2);
+ _video_texture->bind();
+ glDrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_video_texture * sizeof(int)));
+ if (_have_subtitle_to_render) {
+ glUniform1i(_fragment_type, 2);
+ _subtitle_texture->bind();
+ glDrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_subtitle_texture * sizeof(int)));
}
-
- if (!_viewer->pad_black() && out_size.height < height) {
- glColor3ub (240, 240, 240);
- int const gap = (height - out_size.height) / 2;
- glBegin (GL_QUADS);
- glVertex2f (0, 0);
- glVertex2f (width, 0);
- glVertex2f (width, gap);
- glVertex2f (0, gap);
- glEnd ();
- glBegin (GL_QUADS);
- glVertex2f (0, gap + out_size.height + 1);
- glVertex2f (width, gap + out_size.height + 1);
- glVertex2f (width, 2 * gap + out_size.height + 2);
- glVertex2f (0, 2 * gap + out_size.height + 2);
- glEnd ();
- glColor3ub (255, 255, 255);
- }
-
if (_viewer->outline_content()) {
- glColor3ub (255, 0, 0);
- glBegin (GL_LINE_LOOP);
- glVertex2f (inter_position.x, inter_position.y + (height - out_size.height) / 2);
- glVertex2f (inter_position.x + inter_size.width, inter_position.y + (height - out_size.height) / 2);
- glVertex2f (inter_position.x + inter_size.width, inter_position.y + (height - out_size.height) / 2 + inter_size.height);
- glVertex2f (inter_position.x, inter_position.y + (height - out_size.height) / 2 + inter_size.height);
- glEnd ();
- glColor3ub (255, 255, 255);
+ glUniform1i(_fragment_type, 0);
+ glDrawElements (GL_LINES, 8, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_border * sizeof(int)));
+ check_gl_error ("glDrawElements");
}
glFlush();
@@ -263,33 +493,109 @@ DCPOMATIC_ENABLE_WARNINGS
_canvas->SwapBuffers();
}
+
void
-GLVideoView::set_image (shared_ptr<const Image> image)
+GLVideoView::set_image (shared_ptr<const PlayerVideo> pv)
{
- if (!image) {
- _size = optional<dcp::Size>();
- return;
+ shared_ptr<const Image> video = _optimise_for_j2k ? pv->raw_image() : pv->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), VideoRange::FULL, true);
+
+ /* Only the player's black frames should be aligned at this stage, so this should
+ * almost always have no work to do.
+ */
+ video = Image::ensure_alignment (video, Image::Alignment::COMPACT);
+
+ /** If _optimise_for_j2k is true we render a XYZ image, doing the colourspace
+ * conversion, scaling and video range conversion in the GL shader.
+ * Othewise we render a RGB image without any shader-side processing.
+ */
+
+ /* XXX: video range conversion */
+
+ _video_texture->set (video);
+
+ auto const text = pv->text();
+ _have_subtitle_to_render = static_cast<bool>(text) && _optimise_for_j2k;
+ if (_have_subtitle_to_render) {
+ /* opt: only do this if it's a new subtitle? */
+ DCPOMATIC_ASSERT (text->image->alignment() == Image::Alignment::COMPACT);
+ _subtitle_texture->set (text->image);
}
- DCPOMATIC_ASSERT (image->pixel_format() == AV_PIX_FMT_RGB24);
- DCPOMATIC_ASSERT (!image->aligned());
- if (image->size() != _size) {
- _have_storage = false;
+ auto const canvas_size = _canvas_size.load();
+ int const canvas_width = canvas_size.GetWidth();
+ int const canvas_height = canvas_size.GetHeight();
+ auto inter_position = player_video().first->inter_position();
+ auto inter_size = player_video().first->inter_size();
+ auto out_size = player_video().first->out_size();
+
+ _last_canvas_size.set_next (canvas_size);
+ _last_video_size.set_next (video->size());
+ _last_inter_position.set_next (inter_position);
+ _last_inter_size.set_next (inter_size);
+ _last_out_size.set_next (out_size);
+
+ auto x_pixels_to_gl = [canvas_width](int x) {
+ return (x * 2.0f / canvas_width) - 1.0f;
+ };
+
+ auto y_pixels_to_gl = [canvas_height](int y) {
+ return (y * 2.0f / canvas_height) - 1.0f;
+ };
+
+ if (_last_canvas_size.changed() || _last_inter_position.changed() || _last_inter_size.changed() || _last_out_size.changed()) {
+ float const video_x1 = x_pixels_to_gl(_optimise_for_j2k ? inter_position.x : 0);
+ float const video_x2 = x_pixels_to_gl(_optimise_for_j2k ? (inter_position.x + inter_size.width) : out_size.width);
+ float const video_y1 = y_pixels_to_gl(_optimise_for_j2k ? inter_position.y : 0);
+ float const video_y2 = y_pixels_to_gl(_optimise_for_j2k ? (inter_position.y + inter_size.height) : out_size.height);
+ float video_vertices[] = {
+ // positions // texture coords
+ video_x2, video_y2, 0.0f, 1.0f, 0.0f, // video texture top right (index 0)
+ video_x2, video_y1, 0.0f, 1.0f, 1.0f, // video texture bottom right (index 1)
+ video_x1, video_y1, 0.0f, 0.0f, 1.0f, // video texture bottom left (index 2)
+ video_x1, video_y2, 0.0f, 0.0f, 0.0f, // video texture top left (index 3)
+ };
+
+ glBufferSubData (GL_ARRAY_BUFFER, 0, sizeof(video_vertices), video_vertices);
+ check_gl_error ("glBufferSubData (video)");
+
+ float const border_x1 = x_pixels_to_gl(inter_position.x);
+ float const border_y1 = y_pixels_to_gl(inter_position.y);
+ float const border_x2 = x_pixels_to_gl(inter_position.x + inter_size.width);
+ float const border_y2 = y_pixels_to_gl(inter_position.y + inter_size.height);
+
+ float border_vertices[] = {
+ // positions // texture coords
+ border_x1, border_y1, 0.0f, 0.0f, 0.0f, // border bottom left (index 4)
+ border_x1, border_y2, 0.0f, 0.0f, 0.0f, // border top left (index 5)
+ border_x2, border_y2, 0.0f, 0.0f, 0.0f, // border top right (index 6)
+ border_x2, border_y1, 0.0f, 0.0f, 0.0f, // border bottom right (index 7)
+ };
+
+ glBufferSubData (GL_ARRAY_BUFFER, 8 * 5 * sizeof(float), sizeof(border_vertices), border_vertices);
+ check_gl_error ("glBufferSubData (border)");
}
- _size = image->size ();
- glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
- check_gl_error ("glPixelStorei");
- if (_have_storage) {
- glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, _size->width, _size->height, GL_RGB, GL_UNSIGNED_BYTE, image->data()[0]);
- check_gl_error ("glTexSubImage2D");
- } else {
- glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB8, _size->width, _size->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->data()[0]);
- _have_storage = true;
- check_gl_error ("glTexImage2D");
+ if (_have_subtitle_to_render) {
+ float const subtitle_x1 = x_pixels_to_gl(inter_position.x + text->position.x);
+ float const subtitle_x2 = x_pixels_to_gl(inter_position.x + text->position.x + text->image->size().width);
+ float const subtitle_y1 = y_pixels_to_gl(inter_position.y + text->position.y + text->image->size().height);
+ float const subtitle_y2 = y_pixels_to_gl(inter_position.y + text->position.y);
+
+ float vertices[] = {
+ // positions // texture coords
+ subtitle_x2, subtitle_y1, 0.0f, 1.0f, 0.0f, // subtitle texture top right (index 4)
+ subtitle_x2, subtitle_y2, 0.0f, 1.0f, 1.0f, // subtitle texture bottom right (index 5)
+ subtitle_x1, subtitle_y2, 0.0f, 0.0f, 1.0f, // subtitle texture bottom left (index 6)
+ subtitle_x1, subtitle_y1, 0.0f, 0.0f, 0.0f, // subtitle texture top left (index 7)
+ };
+
+ glBufferSubData (GL_ARRAY_BUFFER, 4 * 5 * sizeof(float), sizeof(vertices), vertices);
+ check_gl_error ("glBufferSubData (subtitle)");
}
+ /* opt: where should these go? */
+
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
check_gl_error ("glTexParameteri");
@@ -299,6 +605,7 @@ GLVideoView::set_image (shared_ptr<const Image> image)
check_gl_error ("glTexParameterf");
}
+
void
GLVideoView::start ()
{
@@ -321,7 +628,7 @@ void
GLVideoView::thread_playing ()
{
if (length() != dcpomatic::DCPTime()) {
- dcpomatic::DCPTime const next = position() + one_video_frame();
+ auto const next = position() + one_video_frame();
if (next >= length()) {
_viewer->finished ();
@@ -346,9 +653,9 @@ GLVideoView::thread_playing ()
void
GLVideoView::set_image_and_draw ()
{
- shared_ptr<PlayerVideo> pv = player_video().first;
+ auto pv = player_video().first;
if (pv) {
- set_image (pv->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), VideoRange::FULL, false, true));
+ set_image (pv);
draw (pv->inter_position(), pv->inter_size());
_viewer->image_changed (pv);
}
@@ -367,12 +674,10 @@ try
*/
WXGLSetCurrentContext (_context->GetWXGLContext());
#else
- /* We must call this here on Linux otherwise we get no image (for reasons
- * that aren't clear). However, doing ensure_context() from this thread
- * on macOS gives
- * "[NSOpenGLContext setView:] must be called from the main thread".
- */
- ensure_context ();
+ if (!_setup_shaders_done) {
+ setup_shaders ();
+ _setup_shaders_done = true;
+ }
#endif
#if defined(DCPOMATIC_LINUX) && defined(DCPOMATIC_HAVE_GLX_SWAP_INTERVAL_EXT)
@@ -403,12 +708,8 @@ try
_vsync_enabled = true;
#endif
- glGenTextures (1, &_id);
- check_gl_error ("glGenTextures");
- glBindTexture (GL_TEXTURE_2D, _id);
- check_gl_error ("glBindTexture");
- glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
- check_gl_error ("glPixelStorei");
+ _video_texture.reset(new Texture(_optimise_for_j2k ? 2 : 1));
+ _subtitle_texture.reset(new Texture(1));
while (true) {
boost::mutex::scoped_lock lm (_playing_mutex);
@@ -432,7 +733,7 @@ try
* without also deleting the wxGLCanvas.
*/
}
-catch (boost::thread_interrupted& e)
+catch (...)
{
store_current ();
}
@@ -455,3 +756,77 @@ GLVideoView::request_one_shot ()
_thread_work_condition.notify_all ();
}
+
+Texture::Texture (GLint unpack_alignment)
+ : _unpack_alignment (unpack_alignment)
+{
+ glGenTextures (1, &_name);
+ check_gl_error ("glGenTextures");
+}
+
+
+Texture::~Texture ()
+{
+ glDeleteTextures (1, &_name);
+}
+
+
+void
+Texture::bind ()
+{
+ glBindTexture(GL_TEXTURE_2D, _name);
+ check_gl_error ("glBindTexture");
+}
+
+
+void
+Texture::set (shared_ptr<const Image> image)
+{
+ auto const create = !_size || image->size() != _size;
+ _size = image->size();
+
+ glPixelStorei (GL_UNPACK_ALIGNMENT, _unpack_alignment);
+ check_gl_error ("glPixelStorei");
+
+ DCPOMATIC_ASSERT (image->alignment() == Image::Alignment::COMPACT);
+
+ GLint internal_format;
+ GLenum format;
+ GLenum type;
+
+ switch (image->pixel_format()) {
+ case AV_PIX_FMT_BGRA:
+ internal_format = GL_RGBA8;
+ format = GL_BGRA;
+ type = GL_UNSIGNED_BYTE;
+ break;
+ case AV_PIX_FMT_RGBA:
+ internal_format = GL_RGBA8;
+ format = GL_RGBA;
+ type = GL_UNSIGNED_BYTE;
+ break;
+ case AV_PIX_FMT_RGB24:
+ internal_format = GL_RGBA8;
+ format = GL_RGB;
+ type = GL_UNSIGNED_BYTE;
+ break;
+ case AV_PIX_FMT_XYZ12:
+ internal_format = GL_RGBA12;
+ format = GL_RGB;
+ type = GL_UNSIGNED_SHORT;
+ break;
+ default:
+ throw PixelFormatError ("Texture::set", image->pixel_format());
+ }
+
+ bind ();
+
+ if (create) {
+ glTexImage2D (GL_TEXTURE_2D, 0, internal_format, _size->width, _size->height, 0, format, type, image->data()[0]);
+ check_gl_error ("glTexImage2D");
+ } else {
+ glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, _size->width, _size->height, format, type, image->data()[0]);
+ check_gl_error ("glTexSubImage2D");
+ }
+}
+
diff --git a/src/wx/gl_video_view.h b/src/wx/gl_video_view.h
index 36edd6b8b..d7f8429af 100644
--- a/src/wx/gl_video_view.h
+++ b/src/wx/gl_video_view.h
@@ -34,27 +34,51 @@ DCPOMATIC_ENABLE_WARNINGS
#undef Success
#undef Status
+
+class Texture
+{
+public:
+ Texture (GLint unpack_alignment);
+ ~Texture ();
+
+ Texture (Texture const&) = delete;
+ Texture& operator= (Texture const&) = delete;
+
+ void bind ();
+ void set (std::shared_ptr<const Image> image);
+
+private:
+ GLuint _name;
+ GLint _unpack_alignment;
+ boost::optional<dcp::Size> _size;
+};
+
+
class GLVideoView : public VideoView
{
public:
GLVideoView (FilmViewer* viewer, wxWindow* parent);
~GLVideoView ();
- wxWindow* get () const {
+ wxWindow* get () const override {
return _canvas;
}
- void update ();
- void start ();
- void stop ();
+ void update () override;
+ void start () override;
+ void stop () override;
- NextFrameResult display_next_frame (bool);
+ NextFrameResult display_next_frame (bool) override;
bool vsync_enabled () const {
return _vsync_enabled;
}
+ std::map<GLenum, std::string> information () const {
+ return _information;
+ }
+
private:
- void set_image (std::shared_ptr<const Image> image);
+ void set_image (std::shared_ptr<const PlayerVideo> pv);
void set_image_and_draw ();
void draw (Position<int> inter_position, dcp::Size inter_size);
void thread ();
@@ -63,15 +87,43 @@ private:
void check_for_butler_errors ();
void ensure_context ();
void size_changed (wxSizeEvent const &);
+ void setup_shaders ();
+ void set_border_colour (GLuint program);
wxGLCanvas* _canvas;
wxGLContext* _context;
- boost::atomic<wxSize> _canvas_size;
+ template <class T>
+ class Last
+ {
+ public:
+ void set_next (T const& next) {
+ _next = next;
+ }
+
+ bool changed () const {
+ return !_value || *_value != _next;
+ }
+
+ void update () {
+ _value = _next;
+ }
+
+ private:
+ boost::optional<T> _value;
+ T _next;
+ };
+
+ Last<wxSize> _last_canvas_size;
+ Last<dcp::Size> _last_video_size;
+ Last<Position<int>> _last_inter_position;
+ Last<dcp::Size> _last_inter_size;
+ Last<dcp::Size> _last_out_size;
- GLuint _id;
- boost::optional<dcp::Size> _size;
- bool _have_storage;
+ boost::atomic<wxSize> _canvas_size;
+ std::unique_ptr<Texture> _video_texture;
+ std::unique_ptr<Texture> _subtitle_texture;
+ bool _have_subtitle_to_render = false;
bool _vsync_enabled;
boost::thread _thread;
@@ -80,5 +132,11 @@ private:
boost::atomic<bool> _playing;
boost::atomic<bool> _one_shot;
+ GLuint _vao;
+ GLint _fragment_type;
+ bool _setup_shaders_done = false;
+
std::shared_ptr<wxTimer> _timer;
+
+ std::map<GLenum, std::string> _information;
};
diff --git a/src/wx/playlist_controls.cc b/src/wx/playlist_controls.cc
index d65cb0fcc..129e0ceca 100644
--- a/src/wx/playlist_controls.cc
+++ b/src/wx/playlist_controls.cc
@@ -109,7 +109,7 @@ PlaylistControls::PlaylistControls (wxWindow* parent, shared_ptr<FilmViewer> vie
_previous_button->Bind (wxEVT_BUTTON, boost::bind(&PlaylistControls::previous_clicked, this));
_spl_view->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&PlaylistControls::spl_selection_changed, this));
_spl_view->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&PlaylistControls::spl_selection_changed, this));
- _viewer->Finished.connect (boost::bind(&PlaylistControls::viewer_finished, this));
+ viewer->Finished.connect (boost::bind(&PlaylistControls::viewer_finished, this));
_refresh_spl_view->Bind (wxEVT_BUTTON, boost::bind(&PlaylistControls::update_playlist_directory, this));
_refresh_content_view->Bind (wxEVT_BUTTON, boost::bind(&ContentView::update, _content_view));
@@ -148,18 +148,26 @@ PlaylistControls::deselect_playlist ()
void
PlaylistControls::play_clicked ()
{
- _viewer->start ();
+ auto viewer = _viewer.lock ();
+ if (viewer) {
+ viewer->start ();
+ }
}
void
PlaylistControls::setup_sensitivity ()
{
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
Controls::setup_sensitivity ();
bool const active_job = _active_job && *_active_job != "examine_content";
bool const c = _film && !_film->content().empty() && !active_job;
- _play_button->Enable (c && !_viewer->playing());
- _pause_button->Enable (_viewer->playing());
- _spl_view->Enable (!_viewer->playing());
+ _play_button->Enable (c && !viewer->playing());
+ _pause_button->Enable (viewer->playing());
+ _spl_view->Enable (!viewer->playing());
_next_button->Enable (can_do_next());
_previous_button->Enable (can_do_previous());
}
@@ -167,14 +175,22 @@ PlaylistControls::setup_sensitivity ()
void
PlaylistControls::pause_clicked ()
{
- _viewer->stop ();
+ auto viewer = _viewer.lock ();
+ if (viewer) {
+ viewer->stop ();
+ }
}
void
PlaylistControls::stop_clicked ()
{
- _viewer->stop ();
- _viewer->seek (DCPTime(), true);
+ auto viewer = _viewer.lock ();
+ if (!viewer) {
+ return;
+ }
+
+ viewer->stop ();
+ viewer->seek (DCPTime(), true);
if (_selected_playlist) {
_selected_playlist_position = 0;
update_current_content ();
@@ -436,7 +452,8 @@ PlaylistControls::update_current_content ()
void
PlaylistControls::viewer_finished ()
{
- if (!_selected_playlist) {
+ auto viewer = _viewer.lock ();
+ if (!_selected_playlist || !viewer) {
return;
}
@@ -444,7 +461,7 @@ PlaylistControls::viewer_finished ()
if (_selected_playlist_position < int(_playlists[*_selected_playlist].get().size())) {
/* Next piece of content on the SPL */
update_current_content ();
- _viewer->start ();
+ viewer->start ();
} else {
/* Finished the whole SPL */
_selected_playlist_position = 0;
diff --git a/src/wx/simple_video_view.cc b/src/wx/simple_video_view.cc
index f5499ad9d..1ac56bbfe 100644
--- a/src/wx/simple_video_view.cc
+++ b/src/wx/simple_video_view.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2019-2021 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -18,21 +18,23 @@
*/
-#include "simple_video_view.h"
+
+#include "closed_captions_dialog.h"
#include "film_viewer.h"
+#include "simple_video_view.h"
#include "wx_util.h"
-#include "closed_captions_dialog.h"
-#include "lib/image.h"
-#include "lib/dcpomatic_log.h"
#include "lib/butler.h"
+#include "lib/dcpomatic_log.h"
+#include "lib/image.h"
#include <dcp/util.h>
#include <wx/wx.h>
#include <boost/bind/bind.hpp>
+
using std::max;
+using std::shared_ptr;
using std::string;
using boost::optional;
-using std::shared_ptr;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
#endif
@@ -57,18 +59,21 @@ SimpleVideoView::SimpleVideoView (FilmViewer* viewer, wxWindow* parent)
_timer.Bind (wxEVT_TIMER, boost::bind(&SimpleVideoView::timer, this));
}
+
void
SimpleVideoView::paint ()
{
_state_timer.set("paint-panel");
wxPaintDC dc (_panel);
- dcp::Size const out_size = _viewer->out_size ();
- wxSize const panel_size = _panel->GetSize ();
+ auto const panel_size = _panel->GetSize ();
- if (!out_size.width || !out_size.height || !_image || out_size != _image->size()) {
+ dcp::Size out_size;
+ if (!_image) {
dc.Clear ();
} else {
+ DCPOMATIC_ASSERT (_image->alignment() == Image::Alignment::COMPACT);
+ out_size = _image->size();
wxImage frame (out_size.width, out_size.height, _image->data()[0], true);
wxBitmap frame_bitmap (frame);
dc.DrawBitmap (frame_bitmap, 0, max(0, (panel_size.GetHeight() - out_size.height) / 2));
@@ -112,6 +117,7 @@ SimpleVideoView::paint ()
_state_timer.unset();
}
+
void
SimpleVideoView::refresh_panel ()
{
@@ -121,6 +127,7 @@ SimpleVideoView::refresh_panel ()
_state_timer.unset ();
}
+
void
SimpleVideoView::timer ()
{
@@ -144,6 +151,7 @@ SimpleVideoView::timer ()
}
}
+
void
SimpleVideoView::start ()
{
@@ -151,6 +159,7 @@ SimpleVideoView::start ()
timer ();
}
+
/** Try to get a frame from the butler and display it.
* @param non_blocking true to return false quickly if no video is available quickly (i.e. we are waiting for the butler).
* false to ask the butler to block until it has video (unless it is suspended).
@@ -175,11 +184,12 @@ SimpleVideoView::display_next_frame (bool non_blocking)
return SUCCESS;
}
+
void
SimpleVideoView::update ()
{
if (!player_video().first) {
- set_image (shared_ptr<Image>());
+ _image.reset ();
refresh_panel ();
return;
}
@@ -212,9 +222,7 @@ SimpleVideoView::update ()
_state_timer.set ("get image");
- set_image (
- player_video().first->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), VideoRange::FULL, false, true)
- );
+ _image = player_video().first->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), VideoRange::FULL, true);
_state_timer.set ("ImageChanged");
_viewer->image_changed (player_video().first);
diff --git a/src/wx/simple_video_view.h b/src/wx/simple_video_view.h
index 31756b5d8..cbb162023 100644
--- a/src/wx/simple_video_view.h
+++ b/src/wx/simple_video_view.h
@@ -33,19 +33,15 @@ class SimpleVideoView : public VideoView
public:
SimpleVideoView (FilmViewer* viewer, wxWindow* parent);
- wxWindow* get () const {
+ wxWindow* get () const override {
return _panel;
}
- void update ();
- void start ();
- NextFrameResult display_next_frame (bool non_blocking);
+ void update () override;
+ void start () override;
+ NextFrameResult display_next_frame (bool non_blocking) override;
private:
- void set_image (std::shared_ptr<const Image> image) {
- _image = image;
- }
-
void refresh_panel ();
void paint ();
void timer ();
diff --git a/src/wx/standard_controls.cc b/src/wx/standard_controls.cc
index 1e4ecc8d7..6196c1b5c 100644
--- a/src/wx/standard_controls.cc
+++ b/src/wx/standard_controls.cc
@@ -63,14 +63,15 @@ StandardControls::play_clicked ()
void
StandardControls::check_play_state ()
{
- if (!_film || _film->video_frame_rate() == 0) {
+ auto viewer = _viewer.lock ();
+ if (!_film || _film->video_frame_rate() == 0 || !viewer) {
return;
}
if (_play_button->GetValue()) {
- _viewer->start ();
+ viewer->start ();
} else {
- _viewer->stop ();
+ viewer->stop ();
}
}
diff --git a/src/wx/system_information_dialog.cc b/src/wx/system_information_dialog.cc
index 968cd5740..1c8dd8d00 100644
--- a/src/wx/system_information_dialog.cc
+++ b/src/wx/system_information_dialog.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2019-2021 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -18,10 +18,12 @@
*/
+
+#include "film_viewer.h"
+#include "gl_video_view.h"
#include "system_information_dialog.h"
#include "wx_util.h"
-#include "gl_video_view.h"
-#include "film_viewer.h"
+
#ifdef DCPOMATIC_OSX
#include <OpenGL/glu.h>
@@ -31,27 +33,42 @@
#include <GL/glext.h>
#endif
+
using std::string;
using std::weak_ptr;
using std::shared_ptr;
+
SystemInformationDialog::SystemInformationDialog (wxWindow* parent, weak_ptr<FilmViewer> weak_viewer)
: TableDialog (parent, _("System information"), 2, 1, false)
{
- shared_ptr<FilmViewer> viewer = weak_viewer.lock ();
- GLVideoView const * gl = viewer ? dynamic_cast<GLVideoView const *>(viewer->video_view()) : 0;
+ auto viewer = weak_viewer.lock ();
+ shared_ptr<const GLVideoView> gl;
+ if (viewer) {
+ gl = std::dynamic_pointer_cast<const GLVideoView>(viewer->video_view());
+ }
if (!gl) {
add (_("OpenGL version"), true);
add (_("unknown (OpenGL not enabled in DCP-o-matic)"), false);
} else {
- add (_("OpenGL version"), true);
- char const * v = (char const *) glGetString (GL_VERSION);
- if (v) {
- add (std_to_wx(v), false);
- } else {
- add (_("unknown"), false);
- }
+
+ auto information = gl->information();
+ auto add_string = [this, &information](GLenum name, wxString label) {
+ add (label, true);
+ auto i = information.find(name);
+ if (i != information.end()) {
+ add (std_to_wx(i->second), false);
+ } else {
+ add (_("unknown"), false);
+ }
+ };
+
+ add_string (GL_VENDOR, _("Vendor"));
+ add_string (GL_RENDERER, _("Renderer"));
+ add_string (GL_VERSION, _("Version"));
+ add_string (GL_SHADING_LANGUAGE_VERSION, _("Shading language version"));
+
add (_("vsync"), true);
add (gl->vsync_enabled() ? _("enabled") : _("not enabled"), false);
}
diff --git a/src/wx/system_information_dialog.h b/src/wx/system_information_dialog.h
index 5b9efa234..49e617a6e 100644
--- a/src/wx/system_information_dialog.h
+++ b/src/wx/system_information_dialog.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2019-2021 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -25,9 +25,9 @@
class FilmViewer;
+
class SystemInformationDialog : public TableDialog
{
public:
SystemInformationDialog (wxWindow* parent, std::weak_ptr<FilmViewer> viewer);
-
};
diff --git a/src/wx/video_view.cc b/src/wx/video_view.cc
index 4d80e2535..1d10beb30 100644
--- a/src/wx/video_view.cc
+++ b/src/wx/video_view.cc
@@ -28,7 +28,6 @@
#include <sys/time.h>
-using std::pair;
using std::shared_ptr;
using boost::optional;
diff --git a/src/wx/video_view.h b/src/wx/video_view.h
index 9517a3bf4..5353f213f 100644
--- a/src/wx/video_view.h
+++ b/src/wx/video_view.h
@@ -127,6 +127,10 @@ public:
_three_d = t;
}
+ void set_optimise_for_j2k (bool o) {
+ _optimise_for_j2k = o;
+ }
+
protected:
NextFrameResult get_next_frame (bool non_blocking);
boost::optional<int> time_until_next_frame () const;
@@ -168,6 +172,8 @@ protected:
StateTimer _state_timer;
+ bool _optimise_for_j2k = false;
+
private:
/** Mutex protecting all the state in this class */
mutable boost::mutex _mutex;
diff --git a/src/wx/video_waveform_plot.cc b/src/wx/video_waveform_plot.cc
index 2e45f3493..07b2955b3 100644
--- a/src/wx/video_waveform_plot.cc
+++ b/src/wx/video_waveform_plot.cc
@@ -155,7 +155,7 @@ VideoWaveformPlot::create_waveform ()
auto const image_size = _image->size();
int const waveform_height = GetSize().GetHeight() - _vertical_margin * 2;
- _waveform = make_shared<Image>(AV_PIX_FMT_RGB24, dcp::Size (image_size.width, waveform_height), true);
+ _waveform = make_shared<Image>(AV_PIX_FMT_RGB24, dcp::Size (image_size.width, waveform_height), Image::Alignment::PADDED);
for (int x = 0; x < image_size.width; ++x) {
@@ -182,7 +182,7 @@ VideoWaveformPlot::create_waveform ()
_waveform = _waveform->scale (
dcp::Size (GetSize().GetWidth() - _x_axis_width, waveform_height),
- dcp::YUVToRGB::REC709, AV_PIX_FMT_RGB24, false, false
+ dcp::YUVToRGB::REC709, AV_PIX_FMT_RGB24, Image::Alignment::COMPACT, false
);
}
diff --git a/src/wx/wscript b/src/wx/wscript
index b868292f5..50c078f2b 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -209,7 +209,7 @@ def configure(conf):
mandatory=True)
if conf.env.TARGET_LINUX:
- conf.env.append_value('CXXFLAGS', ['-DGLX_GLXEXT_PROTOTYPES'])
+ conf.env.append_value('CXXFLAGS', ['-DGL_GLEXT_PROTOTYPES', '-DGLX_GLXEXT_PROTOTYPES'])
if conf.env.TARGET_WINDOWS:
conf.env.append_value('CXXFLAGS', ['-DWGL_WGLEXT_PROTOTYPES'])
@@ -277,6 +277,8 @@ def configure(conf):
if conf.env.TARGET_WINDOWS or conf.env.TARGET_LINUX:
conf.check_cfg(package='gl', args='--cflags --libs', uselib_store='GL', mandatory=True)
conf.check_cfg(package='glu', args='--cflags --libs', uselib_store='GLU', mandatory=True)
+ if conf.env.TARGET_WINDOWS:
+ conf.check_cfg(package='glew', args='--cflags --libs', uselib_store='GLEW', mandatory=True)
else:
conf.env.STLIB_GL = 'gl'
conf.env.STLIB_GLU = 'glu'
@@ -311,7 +313,7 @@ def build(bld):
if bld.env.TARGET_LINUX:
obj.uselib += 'GTK GL GLU '
if bld.env.TARGET_WINDOWS:
- obj.uselib += 'WINSOCK2 OLE32 DSOUND WINMM KSUSER GL GLU '
+ obj.uselib += 'WINSOCK2 OLE32 DSOUND WINMM KSUSER GL GLU GLEW '
if bld.env.TARGET_OSX:
obj.framework = ['CoreAudio', 'OpenGL']
obj.use = 'libdcpomatic2'