X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fwx%2Fgl_video_view.cc;h=6288a24a3e1e7b350cbd94b593781b5d575ca786;hb=9bfa07293928c371d59db2091ba2b7e715ce5994;hp=7c26ae6169780660c66ada2c5017a5b4d15b2ee3;hpb=e9ae050b0b15c91c3f591ad84938e60d271357b3;p=dcpomatic.git diff --git a/src/wx/gl_video_view.cc b/src/wx/gl_video_view.cc index 7c26ae616..6288a24a3 100644 --- a/src/wx/gl_video_view.cc +++ b/src/wx/gl_video_view.cc @@ -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,26 @@ 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 = 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::setup_shaders () { @@ -289,15 +329,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,11 +414,30 @@ 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 matrix = conversion.xyz_to_rgb (); + GLfloat gl_matrix[] = { + static_cast(matrix(0, 0)), static_cast(matrix(0, 1)), static_cast(matrix(0, 2)), 0.0f, + static_cast(matrix(1, 0)), static_cast(matrix(1, 1)), static_cast(matrix(1, 2)), 0.0f, + static_cast(matrix(2, 0)), static_cast(matrix(2, 1)), static_cast(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); + 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"); } @@ -421,14 +471,19 @@ GLVideoView::draw (Position, 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); + _video_texture->bind(); + glDrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_INT, reinterpret_cast(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(indices_subtitle_texture * sizeof(int))); + } if (_viewer->outline_content()) { - glUniform1i(_draw_border, 1); - glDrawElements (GL_LINES, 8, GL_UNSIGNED_INT, reinterpret_cast(6 * sizeof(int))); + glUniform1i(_fragment_type, 0); + glDrawElements (GL_LINES, 8, GL_UNSIGNED_INT, reinterpret_cast(indices_border * sizeof(int))); check_gl_error ("glDrawElements"); } @@ -440,74 +495,107 @@ GLVideoView::draw (Position, dcp::Size) void -GLVideoView::set_image (shared_ptr image) +GLVideoView::set_image (shared_ptr pv) { - if (!image) { - _size = optional(); - return; - } + shared_ptr video = _optimise_for_j2k ? pv->raw_image() : pv->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), VideoRange::FULL, false, 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_aligned (video, false); - DCPOMATIC_ASSERT (image->pixel_format() == AV_PIX_FMT_RGB24); - DCPOMATIC_ASSERT (!image->aligned()); + /** 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); - if (image->size() != _size) { - _have_storage = false; + auto const text = pv->text(); + _have_subtitle_to_render = static_cast(text) && _optimise_for_j2k; + if (_have_subtitle_to_render) { + /* opt: only do this if it's a new subtitle? */ + DCPOMATIC_ASSERT (!text->image->aligned()); + _subtitle_texture->set (text->image); } - _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_RGBA8, _size->width, _size->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->data()[0]); - check_gl_error ("glTexImage2D"); + 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(); - auto const canvas_size = _canvas_size.load(); - int const canvas_width = canvas_size.GetWidth(); - int const canvas_height = canvas_size.GetHeight(); + _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); - float const image_x = float(_size->width) / canvas_width; - float const image_y = float(_size->height) / canvas_height; + 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; + }; - auto x_pixels_to_gl = [canvas_width](int x) { - return (x * 2.0f / canvas_width) - 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) }; - auto y_pixels_to_gl = [canvas_height](int y) { - return (y * 2.0f / canvas_height) - 1.0f; + 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) }; - auto inter_position = player_video().first->inter_position(); - auto inter_size = player_video().first->inter_size(); + glBufferSubData (GL_ARRAY_BUFFER, 8 * 5 * sizeof(float), sizeof(border_vertices), border_vertices); + check_gl_error ("glBufferSubData (border)"); + } - 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; + 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 - 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) + // 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) }; - /* 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; + 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"); @@ -517,6 +605,7 @@ GLVideoView::set_image (shared_ptr image) check_gl_error ("glTexParameterf"); } + void GLVideoView::start () { @@ -566,7 +655,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 +708,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)); + _subtitle_texture.reset(new Texture(1)); while (true) { boost::mutex::scoped_lock lm (_playing_mutex); @@ -669,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 image) +{ + auto const create = !_size || image->size() != _size; + _size = image->size(); + + glPixelStorei (GL_UNPACK_ALIGNMENT, _unpack_alignment); + check_gl_error ("glPixelStorei"); + + DCPOMATIC_ASSERT (!image->aligned()); + + 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"); + } +} +