Missing include.
[dcpomatic.git] / src / wx / gl_video_view.cc
index d646441b4e70193ea60a2df6dce2ca2726183433..640b6a373a6d0fe2b1e4f631d4f985a1d4f093c6 100644 (file)
 #endif
 
 #include "gl_video_view.h"
+
+/* This will only build on an new-enough wxWidgets: see the comment in gl_video_view.h */
+#if wxCHECK_VERSION(3,1,0)
+
 #include "film_viewer.h"
 #include "wx_util.h"
-#include "lib/image.h"
+#include "lib/butler.h"
+#include "lib/cross.h"
 #include "lib/dcpomatic_assert.h"
+#include "lib/dcpomatic_log.h"
 #include "lib/exceptions.h"
-#include "lib/cross.h"
+#include "lib/image.h"
 #include "lib/player_video.h"
-#include "lib/butler.h"
 #include <boost/bind/bind.hpp>
 #include <iostream>
 
@@ -99,12 +104,15 @@ GLVideoView::GLVideoView (FilmViewer* viewer, wxWindow *parent)
 void
 GLVideoView::size_changed (wxSizeEvent const& ev)
 {
-       _canvas_size = ev.GetSize ();
+       auto const scale = _canvas->GetDPIScaleFactor();
+       int const width = std::round(ev.GetSize().GetWidth() * scale);
+       int const height = std::round(ev.GetSize().GetHeight() * scale);
+       _canvas_size = { width, height };
+       LOG_GENERAL("GLVideoView canvas size changed to %1x%2", width, height);
        Sized ();
 }
 
 
-
 GLVideoView::~GLVideoView ()
 {
        boost::this_thread::disable_interruption dis;
@@ -285,16 +293,20 @@ GLVideoView::ensure_context ()
 
 /* 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 = 6;
+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,    // border line #1
-       5, 6,    // border line #2
-       6, 7,    // border line #3
-       7, 4,    // border line #4
+       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
 };
 
 
@@ -427,7 +439,16 @@ GLVideoView::setup_shaders ()
        check_gl_error ("glGetUniformLocation");
        glUniformMatrix4fv (colour_conversion, 1, GL_TRUE, gl_matrix);
 
-       glLineWidth (2.0f);
+       glLineWidth (1.0f);
+       check_gl_error ("glLineWidth");
+       glEnable (GL_BLEND);
+       check_gl_error ("glEnable");
+       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+       check_gl_error ("glBlendFunc");
+
+       /* Reserve space for the GL_ARRAY_BUFFER */
+       glBufferData(GL_ARRAY_BUFFER, 12 * 5 * sizeof(float), nullptr, GL_STATIC_DRAW);
+       check_gl_error ("glBufferData");
 }
 
 
@@ -443,7 +464,7 @@ GLVideoView::set_border_colour (GLuint program)
 
 
 void
-GLVideoView::draw (Position<int>, dcp::Size)
+GLVideoView::draw ()
 {
        auto pad = pad_colour();
        glClearColor(pad.Red() / 255.0, pad.Green() / 255.0, pad.Blue() / 255.0, 1.0);
@@ -464,9 +485,15 @@ GLVideoView::draw (Position<int>, dcp::Size)
        glBindVertexArray(_vao);
        check_gl_error ("glBindVertexArray");
        glUniform1i(_fragment_type, _optimise_for_j2k ? 1 : 2);
-       glDrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_video_texture));
+       _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->outline_content()) {
-               glUniform1i(_fragment_type, 1);
+               glUniform1i(_fragment_type, 0);
                glDrawElements (GL_LINES, 8, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_border * sizeof(int)));
                check_gl_error ("glDrawElements");
        }
@@ -481,9 +508,12 @@ GLVideoView::draw (Position<int>, dcp::Size)
 void
 GLVideoView::set_image (shared_ptr<const PlayerVideo> pv)
 {
-       auto image = _optimise_for_j2k ? pv->raw_image() : pv->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), VideoRange::FULL, false, true);
+       shared_ptr<const Image> video = _optimise_for_j2k ? pv->raw_image() : pv->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), VideoRange::FULL, true);
 
-       DCPOMATIC_ASSERT (!image->aligned());
+       /* 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.
@@ -491,51 +521,124 @@ GLVideoView::set_image (shared_ptr<const PlayerVideo> pv)
         */
 
        /* XXX: video range conversion */
-       /* XXX: subs */
 
-       auto const changed = _video_texture->set (image);
+       _video_texture->set (video);
 
-       if (changed) {
-               auto const canvas_size = _canvas_size.load();
-               int const canvas_width = canvas_size.GetWidth();
-               int const canvas_height = canvas_size.GetHeight();
+       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);
+       }
 
-               float const image_x = float(image->size().width) / canvas_width;
-               float const image_y = float(image->size().height) / canvas_height;
 
-               auto x_pixels_to_gl = [canvas_width](int x) {
-                       return (x * 2.0f / canvas_width) - 1.0f;
-               };
+       auto const canvas_size = _canvas_size.load();
+       int const canvas_width = canvas_size.GetWidth();
+       int const canvas_height = canvas_size.GetHeight();
+       auto const inter_position = player_video().first->inter_position();
+       auto const inter_size = player_video().first->inter_size();
+       auto const out_size = player_video().first->out_size();
+
+       auto x_offset = std::max(0, (canvas_width - out_size.width) / 2);
+       auto y_offset = std::max(0, (canvas_height - out_size.height) / 2);
+
+       _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);
+
+       class Rectangle
+       {
+       public:
+               Rectangle (wxSize canvas_size, float x, float y, dcp::Size size)
+                       : _canvas_size (canvas_size)
+               {
+                       auto const x1 = x_pixels_to_gl(x);
+                       auto const y1 = y_pixels_to_gl(y);
+                       auto const x2 = x_pixels_to_gl(x + size.width);
+                       auto const y2 = y_pixels_to_gl(y + size.height);
+
+                       /* The texture coordinates here have to account for the fact that when we put images into the texture OpenGL
+                        * expected us to start at the lower left but we actually started at the top left.  So although the
+                        * top of the texture is at 1.0 we pretend it's the other way round.
+                        */
+
+                       // bottom right
+                       _vertices[0] = x2;
+                       _vertices[1] = y2;
+                       _vertices[2] = 0.0f;
+                       _vertices[3] = 1.0f;
+                       _vertices[4] = 1.0f;
+
+                       // top right
+                       _vertices[5] = x2;
+                       _vertices[6] = y1;
+                       _vertices[7] = 0.0f;
+                       _vertices[8] = 1.0f;
+                       _vertices[9] = 0.0f;
+
+                       // top left
+                       _vertices[10] = x1;
+                       _vertices[11] = y1;
+                       _vertices[12] = 0.0f;
+                       _vertices[13] = 0.0f;
+                       _vertices[14] = 0.0f;
+
+                       // bottom left
+                       _vertices[15] = x1;
+                       _vertices[16] = y2;
+                       _vertices[17] = 0.0f;
+                       _vertices[18] = 0.0f;
+                       _vertices[19] = 1.0f;
+               }
 
-               auto y_pixels_to_gl = [canvas_height](int y) {
-                       return (y * 2.0f / canvas_height) - 1.0f;
-               };
+               float const * vertices () const {
+                       return _vertices;
+               }
 
-               auto inter_position = player_video().first->inter_position();
-               auto inter_size = player_video().first->inter_size();
-
-               float const border_x1 = x_pixels_to_gl (inter_position.x) + 1.0f - image_x;
-               float const border_y1 = y_pixels_to_gl (inter_position.y) + 1.0f - image_y;
-               float const border_x2 = x_pixels_to_gl (inter_position.x + inter_size.width) + 1.0f - image_x;
-               float const border_y2 = y_pixels_to_gl (inter_position.y + inter_size.height) + 1.0f - image_y;
-
-               float vertices[] = {
-                       // positions                  // texture coords
-                        image_x,   image_y,   0.0f,  1.0f, 0.0f,   // video texture top right    (index 0)
-                        image_x,  -image_y,   0.0f,  1.0f, 1.0f,   // video texture bottom right (index 1)
-                       -image_x,  -image_y,   0.0f,  0.0f, 1.0f,   // video texture bottom left  (index 2)
-                       -image_x,   image_y,   0.0f,  0.0f, 0.0f,   // video texture top left     (index 3)
-                        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)
-               };
+               int const size () const {
+                       return sizeof(_vertices);
+               }
 
-               /* Set the vertex shader's input data (GL_ARRAY_BUFFER) */
-               glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
-               check_gl_error ("glBufferData");
+       private:
+               /* @param x x position in pixels where 0 is left and canvas_width is right on screen */
+               float x_pixels_to_gl(int x) const {
+                       return (x * 2.0f / _canvas_size.GetWidth()) - 1.0f;
+               }
+
+               /* @param y y position in pixels where 0 is top and canvas_height is bottom on screen */
+               float y_pixels_to_gl(int y) const {
+                       return 1.0f - (y * 2.0f / _canvas_size.GetHeight());
+               }
+
+               wxSize _canvas_size;
+               float _vertices[20];
+       };
+
+       if (_last_canvas_size.changed() || _last_inter_position.changed() || _last_inter_size.changed() || _last_out_size.changed()) {
+
+               const auto video = _optimise_for_j2k ?
+                       Rectangle(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size)
+                       : Rectangle(canvas_size, x_offset, y_offset, out_size);
+
+               glBufferSubData (GL_ARRAY_BUFFER, 0, video.size(), video.vertices());
+               check_gl_error ("glBufferSubData (video)");
+
+               const auto border = Rectangle(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size);
+               glBufferSubData (GL_ARRAY_BUFFER, 8 * 5 * sizeof(float), border.size(), border.vertices());
+               check_gl_error ("glBufferSubData (border)");
        }
 
+       if (_have_subtitle_to_render) {
+               const auto subtitle = Rectangle(canvas_size, inter_position.x + x_offset + text->position.x, inter_position.y + y_offset + text->position.y, text->image->size());
+               glBufferSubData (GL_ARRAY_BUFFER, 4 * 5 * sizeof(float), subtitle.size(), subtitle.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");
@@ -596,7 +699,11 @@ GLVideoView::set_image_and_draw ()
        auto pv = player_video().first;
        if (pv) {
                set_image (pv);
-               draw (pv->inter_position(), pv->inter_size());
+       }
+
+       draw ();
+
+       if (pv) {
                _viewer->image_changed (pv);
        }
 }
@@ -649,7 +756,7 @@ try
 #endif
 
        _video_texture.reset(new Texture(_optimise_for_j2k ? 2 : 1));
-       _video_texture->bind();
+       _subtitle_texture.reset(new Texture(1));
 
        while (true) {
                boost::mutex::scoped_lock lm (_playing_mutex);
@@ -719,7 +826,7 @@ Texture::bind ()
 }
 
 
-bool
+void
 Texture::set (shared_ptr<const Image> image)
 {
        auto const create = !_size || image->size() != _size;
@@ -728,17 +835,46 @@ Texture::set (shared_ptr<const Image> image)
        glPixelStorei (GL_UNPACK_ALIGNMENT, _unpack_alignment);
        check_gl_error ("glPixelStorei");
 
-       auto const format = image->pixel_format() == AV_PIX_FMT_RGB24 ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT;
-       auto const internal_format = image->pixel_format() == AV_PIX_FMT_RGB24 ? GL_RGBA8 : GL_RGBA12;
+       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, GL_RGB, format, image->data()[0]);
+               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, GL_RGB, format, image->data()[0]);
+               glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, _size->width, _size->height, format, type, image->data()[0]);
                check_gl_error ("glTexSubImage2D");
        }
-
-       return create;
 }
 
+#endif