Move some stuff inside Texture.
[dcpomatic.git] / src / wx / gl_video_view.cc
index 7c26ae6169780660c66ada2c5017a5b4d15b2ee3..d646441b4e70193ea60a2df6dce2ca2726183433 100644 (file)
@@ -74,7 +74,6 @@ 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)
@@ -114,8 +113,6 @@ GLVideoView::~GLVideoView ()
                _thread.interrupt ();
                _thread.join ();
        } catch (...) {}
-
-       glDeleteTextures (1, &_texture);
 }
 
 void
@@ -189,12 +186,22 @@ static constexpr char fragment_source[] =
 "in vec2 TexCoord;\n"
 "\n"
 "uniform sampler2D texture_sampler;\n"
-"uniform int draw_border;\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"
@@ -241,10 +248,23 @@ static constexpr char fragment_source[] =
 "\n"
 "void main()\n"
 "{\n"
-"      if (draw_border == 1) {\n"
-"              FragColor = border_colour;\n"
-"      } else {\n"
-"              FragColor = texture_bicubic(texture_sampler, TexCoord);\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";
 
@@ -262,6 +282,22 @@ GLVideoView::ensure_context ()
        }
 }
 
+
+/* Offset of video texture triangles in indices */
+static constexpr int indices_video_texture = 0;
+/* Offset of border lines in indices */
+static constexpr int indices_border = 6;
+
+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
+};
+
+
 void
 GLVideoView::setup_shaders ()
 {
@@ -289,15 +325,6 @@ GLVideoView::setup_shaders ()
        get_information (GL_VERSION);
        get_information (GL_SHADING_LANGUAGE_VERSION);
 
-       unsigned int indices[] = {
-               0, 1, 3, // texture triangle #1
-               1, 2, 3, // texture triangle #2
-               4, 5,    // border line #1
-               5, 6,    // border line #2
-               6, 7,    // border line #3
-               7, 4,    // border line #4
-       };
-
        glGenVertexArrays(1, &_vao);
        check_gl_error ("glGenVertexArrays");
        GLuint vbo;
@@ -383,10 +410,23 @@ GLVideoView::setup_shaders ()
 
        glUseProgram (program);
 
-       _draw_border = glGetUniformLocation (program, "draw_border");
+       _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);
 }
 
@@ -421,14 +461,13 @@ GLVideoView::draw (Position<int>, dcp::Size)
        glViewport (0, 0, width, height);
        check_gl_error ("glViewport");
 
-       glBindTexture(GL_TEXTURE_2D, _texture);
        glBindVertexArray(_vao);
        check_gl_error ("glBindVertexArray");
-       glUniform1i(_draw_border, 0);
-       glDrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+       glUniform1i(_fragment_type, _optimise_for_j2k ? 1 : 2);
+       glDrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_video_texture));
        if (_viewer->outline_content()) {
-               glUniform1i(_draw_border, 1);
-               glDrawElements (GL_LINES, 8, GL_UNSIGNED_INT, reinterpret_cast<void*>(6 * sizeof(int)));
+               glUniform1i(_fragment_type, 1);
+               glDrawElements (GL_LINES, 8, GL_UNSIGNED_INT, reinterpret_cast<void*>(indices_border * sizeof(int)));
                check_gl_error ("glDrawElements");
        }
 
@@ -440,38 +479,29 @@ GLVideoView::draw (Position<int>, dcp::Size)
 
 
 void
-GLVideoView::set_image (shared_ptr<const Image> image)
+GLVideoView::set_image (shared_ptr<const PlayerVideo> pv)
 {
-       if (!image) {
-               _size = optional<dcp::Size>();
-               return;
-       }
-
+       auto image = _optimise_for_j2k ? pv->raw_image() : pv->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), VideoRange::FULL, false, true);
 
-       DCPOMATIC_ASSERT (image->pixel_format() == AV_PIX_FMT_RGB24);
        DCPOMATIC_ASSERT (!image->aligned());
 
-       if (image->size() != _size) {
-               _have_storage = false;
-       }
+       /** 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.
+        */
 
-       _size = image->size ();
-       glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
-       check_gl_error ("glPixelStorei");
+       /* XXX: video range conversion */
+       /* XXX: subs */
 
-       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_RGBA8, _size->width, _size->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->data()[0]);
-               check_gl_error ("glTexImage2D");
+       auto const changed = _video_texture->set (image);
 
+       if (changed) {
                auto const canvas_size = _canvas_size.load();
                int const canvas_width = canvas_size.GetWidth();
                int const canvas_height = canvas_size.GetHeight();
 
-               float const image_x = float(_size->width) / canvas_width;
-               float const image_y = float(_size->height) / canvas_height;
+               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;
@@ -491,21 +521,19 @@ GLVideoView::set_image (shared_ptr<const Image> image)
 
                float vertices[] = {
                        // positions                  // texture coords
-                        image_x,   image_y,   0.0f,  1.0f, 0.0f,   // top right           (index 0)
-                        image_x,  -image_y,   0.0f,  1.0f, 1.0f,   // bottom right        (index 1)
-                       -image_x,  -image_y,   0.0f,  0.0f, 1.0f,   // bottom left         (index 2)
-                       -image_x,   image_y,   0.0f,  0.0f, 0.0f,   // 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)
+                        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)
                };
 
                /* Set the vertex shader's input data (GL_ARRAY_BUFFER) */
                glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
                check_gl_error ("glBufferData");
-
-               _have_storage = true;
        }
 
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
@@ -517,6 +545,7 @@ GLVideoView::set_image (shared_ptr<const Image> image)
        check_gl_error ("glTexParameterf");
 }
 
+
 void
 GLVideoView::start ()
 {
@@ -566,7 +595,7 @@ GLVideoView::set_image_and_draw ()
 {
        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);
        }
@@ -619,10 +648,8 @@ try
        _vsync_enabled = true;
 #endif
 
-       glGenTextures (1, &_texture);
-       check_gl_error ("glGenTextures");
-       glBindTexture (GL_TEXTURE_2D, _texture);
-       check_gl_error ("glBindTexture");
+       _video_texture.reset(new Texture(_optimise_for_j2k ? 2 : 1));
+       _video_texture->bind();
 
        while (true) {
                boost::mutex::scoped_lock lm (_playing_mutex);
@@ -669,3 +696,49 @@ 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");
+}
+
+
+bool
+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");
+
+       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;
+
+       if (create) {
+               glTexImage2D (GL_TEXTURE_2D, 0, internal_format, _size->width, _size->height, 0, GL_RGB, format, 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]);
+               check_gl_error ("glTexSubImage2D");
+       }
+
+       return create;
+}
+