X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fwx%2Fgl_video_view.cc;h=7bf9e3adc6d8e880403a601f5b268c6e21bffeba;hb=92933c13e9233149e4e80244d92da81b70072214;hp=d47ad87f48cdfbe788bd9151d04920c7cd5bb0ac;hpb=4f0575fcb518d959e8dcf581ec8181609782b4ef;p=dcpomatic.git diff --git a/src/wx/gl_video_view.cc b/src/wx/gl_video_view.cc index d47ad87f4..7bf9e3adc 100644 --- a/src/wx/gl_video_view.cc +++ b/src/wx/gl_video_view.cc @@ -27,7 +27,7 @@ #include "lib/cross.h" #include "lib/player_video.h" #include "lib/butler.h" -#include +#include #include #ifdef DCPOMATIC_OSX @@ -40,7 +40,6 @@ #ifdef DCPOMATIC_LINUX #include #include -#include #endif #ifdef DCPOMATIC_WINDOWS @@ -50,64 +49,58 @@ #endif using std::cout; -using boost::shared_ptr; +using std::shared_ptr; using boost::optional; +#if BOOST_VERSION >= 106100 +using namespace boost::placeholders; +#endif + + +static void +check_gl_error (char const * last) +{ + GLenum const e = glGetError (); + if (e != GL_NO_ERROR) { + throw GLError (last, e); + } +} + GLVideoView::GLVideoView (FilmViewer* viewer, wxWindow *parent) : VideoView (viewer) + , _context (nullptr) , _have_storage (false) , _vsync_enabled (false) - , _thread (0) , _playing (false) , _one_shot (false) { _canvas = new wxGLCanvas (parent, wxID_ANY, 0, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE); _canvas->Bind (wxEVT_PAINT, boost::bind(&GLVideoView::update, this)); - _canvas->Bind (wxEVT_SIZE, boost::bind(boost::ref(Sized))); - _canvas->Bind (wxEVT_CREATE, boost::bind(&GLVideoView::create, this)); + _canvas->Bind (wxEVT_SIZE, boost::bind(&GLVideoView::size_changed, this, _1)); _canvas->Bind (wxEVT_TIMER, boost::bind(&GLVideoView::check_for_butler_errors, this)); _timer.reset (new wxTimer(_canvas)); _timer->Start (2000); +} -#if defined(DCPOMATIC_LINUX) && defined(DCPOMATIC_HAVE_GLX_SWAP_INTERVAL_EXT) - if (_canvas->IsExtensionSupported("GLX_EXT_swap_control")) { - /* Enable vsync */ - Display* dpy = wxGetX11Display(); - glXSwapIntervalEXT (dpy, DefaultScreen(dpy), 1); - _vsync_enabled = true; - } -#endif -#ifdef DCPOMATIC_WINDOWS - if (_canvas->IsExtensionSupported("WGL_EXT_swap_control")) { - /* Enable vsync */ - PFNWGLSWAPINTERVALEXTPROC swap = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT"); - if (swap) { - swap (1); - _vsync_enabled = true; - } - } +void +GLVideoView::size_changed (wxSizeEvent const& ev) +{ + _canvas_size = ev.GetSize (); + Sized (); +} -#endif -#ifdef DCPOMATIC_OSX - /* Enable vsync */ - GLint swapInterval = 1; - CGLSetParameter (CGLGetCurrentContext(), kCGLCPSwapInterval, &swapInterval); - _vsync_enabled = true; -#endif - - glGenTextures (1, &_id); - glBindTexture (GL_TEXTURE_2D, _id); - glPixelStorei (GL_UNPACK_ALIGNMENT, 1); -} GLVideoView::~GLVideoView () { - _thread->interrupt (); - _thread->join (); - delete _thread; + boost::this_thread::disable_interruption dis; + + try { + _thread.interrupt (); + _thread.join (); + } catch (...) {} glDeleteTextures (1, &_id); } @@ -115,32 +108,48 @@ GLVideoView::~GLVideoView () void GLVideoView::check_for_butler_errors () { + if (!_viewer->butler()) { + return; + } + try { _viewer->butler()->rethrow (); } catch (DecodeError& e) { error_dialog (get(), e.what()); + } catch (dcp::ReadError& e) { + error_dialog (get(), wxString::Format(_("Could not read DCP: %s"), std_to_wx(e.what()))); } } -static void -check_gl_error (char const * last) + +/** Called from the UI thread */ +void +GLVideoView::update () { - GLenum const e = glGetError (); - if (e != GL_NO_ERROR) { - throw GLError (last, e); + if (!_canvas->IsShownOnScreen()) { + return; } + +#ifdef DCPOMATIC_OSX + /* macOS gives errors if we don't do this (and therefore [NSOpenGLContext setView:]) from the main thread */ + ensure_context (); +#endif + + if (!_thread.joinable()) { + _thread = boost::thread (boost::bind(&GLVideoView::thread, this)); + } + + request_one_shot (); } + void -GLVideoView::update () +GLVideoView::ensure_context () { - { - boost::mutex::scoped_lock lm (_canvas_mutex); - if (!_canvas->IsShownOnScreen()) { - return; - } + if (!_context) { + _context = new wxGLContext (_canvas); + _canvas->SetCurrent (*_context); } - request_one_shot (); } void @@ -159,22 +168,22 @@ GLVideoView::draw (Position inter_position, dcp::Size inter_size) check_gl_error ("glDisable GL_DEPTH_TEST"); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - wxSize canvas_size; - { - boost::mutex::scoped_lock lm (_canvas_mutex); - canvas_size = _canvas->GetSize (); - } + auto const size = _canvas_size.load(); + int const width = size.GetWidth(); + int const height = size.GetHeight(); - if (canvas_size.GetWidth() < 64 || canvas_size.GetHeight() < 0) { + if (width < 64 || height < 0) { return; } - glViewport (0, 0, canvas_size.GetWidth(), canvas_size.GetHeight()); + glViewport (0, 0, width, height); check_gl_error ("glViewport"); glMatrixMode (GL_PROJECTION); glLoadIdentity (); - gluOrtho2D (0, canvas_size.GetWidth(), canvas_size.GetHeight(), 0); +DCPOMATIC_DISABLE_WARNINGS + gluOrtho2D (0, width, height, 0); +DCPOMATIC_ENABLE_WARNINGS check_gl_error ("gluOrtho2d"); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); @@ -207,31 +216,31 @@ GLVideoView::draw (Position inter_position, dcp::Size inter_size) glEnd (); } - if (!_viewer->pad_black() && out_size.width < canvas_size.GetWidth()) { + 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 (canvas_size.GetWidth(), 0); - glVertex2f (canvas_size.GetWidth(), canvas_size.GetHeight()); - glVertex2f (out_size.width, canvas_size.GetHeight()); + glVertex2f (width, 0); + glVertex2f (width, height); + glVertex2f (out_size.width, height); glEnd (); glColor3ub (255, 255, 255); } - if (!_viewer->pad_black() && out_size.height < canvas_size.GetHeight()) { + if (!_viewer->pad_black() && out_size.height < height) { glColor3ub (240, 240, 240); - int const gap = (canvas_size.GetHeight() - out_size.height) / 2; + int const gap = (height - out_size.height) / 2; glBegin (GL_QUADS); glVertex2f (0, 0); - glVertex2f (canvas_size.GetWidth(), 0); - glVertex2f (canvas_size.GetWidth(), gap); + glVertex2f (width, 0); + glVertex2f (width, gap); glVertex2f (0, gap); glEnd (); glBegin (GL_QUADS); glVertex2f (0, gap + out_size.height + 1); - glVertex2f (canvas_size.GetWidth(), gap + out_size.height + 1); - glVertex2f (canvas_size.GetWidth(), 2 * gap + out_size.height + 2); + 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); @@ -240,17 +249,17 @@ GLVideoView::draw (Position inter_position, dcp::Size inter_size) if (_viewer->outline_content()) { glColor3ub (255, 0, 0); glBegin (GL_LINE_LOOP); - glVertex2f (inter_position.x, inter_position.y + (canvas_size.GetHeight() - out_size.height) / 2); - glVertex2f (inter_position.x + inter_size.width, inter_position.y + (canvas_size.GetHeight() - out_size.height) / 2); - glVertex2f (inter_position.x + inter_size.width, inter_position.y + (canvas_size.GetHeight() - out_size.height) / 2 + inter_size.height); - glVertex2f (inter_position.x, inter_position.y + (canvas_size.GetHeight() - out_size.height) / 2 + inter_size.height); + 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); } glFlush(); + check_gl_error ("glFlush"); - boost::mutex::scoped_lock lm (_canvas_mutex); _canvas->SwapBuffers(); } @@ -297,7 +306,7 @@ GLVideoView::start () boost::mutex::scoped_lock lm (_playing_mutex); _playing = true; - _playing_condition.notify_all (); + _thread_work_condition.notify_all (); } void @@ -307,51 +316,112 @@ GLVideoView::stop () _playing = false; } + +void +GLVideoView::thread_playing () +{ + if (length() != dcpomatic::DCPTime()) { + dcpomatic::DCPTime const next = position() + one_video_frame(); + + if (next >= length()) { + _viewer->finished (); + return; + } + + get_next_frame (false); + set_image_and_draw (); + } + + while (true) { + optional n = time_until_next_frame(); + if (!n || *n > 5) { + break; + } + get_next_frame (true); + add_dropped (); + } +} + + +void +GLVideoView::set_image_and_draw () +{ + shared_ptr pv = player_video().first; + if (pv) { + set_image (pv->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), VideoRange::FULL, false, true)); + draw (pv->inter_position(), pv->inter_size()); + _viewer->image_changed (pv); + } +} + + void GLVideoView::thread () try { - { - boost::mutex::scoped_lock lm (_canvas_mutex); - _context = new wxGLContext (_canvas); //local - _canvas->SetCurrent (*_context); + start_of_thread ("GLVideoView"); + +#if defined(DCPOMATIC_OSX) + /* Without this we see errors like + * ../src/osx/cocoa/glcanvas.mm(194): assert ""context"" failed in SwapBuffers(): should have current context [in thread 700006970000] + */ + 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 (); +#endif + +#if defined(DCPOMATIC_LINUX) && defined(DCPOMATIC_HAVE_GLX_SWAP_INTERVAL_EXT) + if (_canvas->IsExtensionSupported("GLX_EXT_swap_control")) { + /* Enable vsync */ + Display* dpy = wxGetX11Display(); + glXSwapIntervalEXT (dpy, DefaultScreen(dpy), 1); + _vsync_enabled = true; } +#endif + +#ifdef DCPOMATIC_WINDOWS + if (_canvas->IsExtensionSupported("WGL_EXT_swap_control")) { + /* Enable vsync */ + PFNWGLSWAPINTERVALEXTPROC swap = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT"); + if (swap) { + swap (1); + _vsync_enabled = true; + } + } + +#endif + +#ifdef DCPOMATIC_OSX + /* Enable vsync */ + GLint swapInterval = 1; + CGLSetParameter (CGLGetCurrentContext(), kCGLCPSwapInterval, &swapInterval); + _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"); while (true) { boost::mutex::scoped_lock lm (_playing_mutex); while (!_playing && !_one_shot) { - _playing_condition.wait (lm); + _thread_work_condition.wait (lm); } - _one_shot = false; lm.unlock (); - Position inter_position; - dcp::Size inter_size; - if (length() != dcpomatic::DCPTime()) { - dcpomatic::DCPTime const next = position() + one_video_frame(); - - if (next >= length()) { - _viewer->finished (); - continue; - } - - get_next_frame (false); - shared_ptr pv = player_video().first; - if (pv) { - set_image (pv->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true)); - inter_position = pv->inter_position(); - inter_size = pv->inter_size(); - } - } - draw (inter_position, inter_size); - - while (true) { - optional n = time_until_next_frame(); - if (!n || *n > 5) { - break; - } - get_next_frame (true); - add_dropped (); + if (_playing) { + thread_playing (); + } else if (_one_shot) { + _one_shot = false; + set_image_and_draw (); } boost::this_thread::interruption_point (); @@ -367,26 +437,21 @@ catch (boost::thread_interrupted& e) store_current (); } -bool + +VideoView::NextFrameResult GLVideoView::display_next_frame (bool non_blocking) { - bool const r = get_next_frame (non_blocking); + NextFrameResult const r = get_next_frame (non_blocking); request_one_shot (); return r; } + void GLVideoView::request_one_shot () { boost::mutex::scoped_lock lm (_playing_mutex); _one_shot = true; - _playing_condition.notify_all (); + _thread_work_condition.notify_all (); } -void -GLVideoView::create () -{ - if (!_thread) { - _thread = new boost::thread (boost::bind(&GLVideoView::thread, this)); - } -}