diff options
| author | Carl Hetherington <cth@carlh.net> | 2021-09-27 13:43:19 +0200 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2021-09-27 13:43:19 +0200 |
| commit | 952084c4221c5708e02c783284cf0f7239c6b4c4 (patch) | |
| tree | c1521dab0586b6c33e02c9338c94abdc6b4c2ea9 /src/wx | |
| parent | 6e3e984162ca7a181bc7c98d90c295e88e4e7f6c (diff) | |
| parent | 81b9ea804cb9953bb1a4074f208f63374adbf09b (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.cc | 109 | ||||
| -rw-r--r-- | src/wx/controls.h | 2 | ||||
| -rw-r--r-- | src/wx/film_viewer.cc | 43 | ||||
| -rw-r--r-- | src/wx/film_viewer.h | 16 | ||||
| -rw-r--r-- | src/wx/gl_video_view.cc | 635 | ||||
| -rw-r--r-- | src/wx/gl_video_view.h | 78 | ||||
| -rw-r--r-- | src/wx/playlist_controls.cc | 37 | ||||
| -rw-r--r-- | src/wx/simple_video_view.cc | 34 | ||||
| -rw-r--r-- | src/wx/simple_video_view.h | 12 | ||||
| -rw-r--r-- | src/wx/standard_controls.cc | 7 | ||||
| -rw-r--r-- | src/wx/system_information_dialog.cc | 41 | ||||
| -rw-r--r-- | src/wx/system_information_dialog.h | 4 | ||||
| -rw-r--r-- | src/wx/video_view.cc | 1 | ||||
| -rw-r--r-- | src/wx/video_view.h | 6 | ||||
| -rw-r--r-- | src/wx/video_waveform_plot.cc | 4 | ||||
| -rw-r--r-- | src/wx/wscript | 6 |
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' |
