X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fwx%2Fgl_video_view.cc;h=29d44fcd2a6bf9bbd0126573b432a7dc815347b5;hb=2d53b0ad98dd010bab33c3db83c89b264ff1e9bb;hp=7fb387f53132d2368dabcb960d38d0b39dcf332b;hpb=56e0452ce0f436e7c54a49c68b272797f92a7ffe;p=dcpomatic.git diff --git a/src/wx/gl_video_view.cc b/src/wx/gl_video_view.cc index 7fb387f53..29d44fcd2 100644 --- a/src/wx/gl_video_view.cc +++ b/src/wx/gl_video_view.cc @@ -24,14 +24,19 @@ #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 #include @@ -74,7 +79,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) @@ -100,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; @@ -187,12 +194,15 @@ static constexpr char fragment_source[] = "in vec2 TexCoord;\n" "\n" "uniform sampler2D texture_sampler;\n" -/* type = 0: draw border - * type = 1: draw XYZ image - * type = 2: draw RGB image +/* type = 0: draw outline content rectangle + * type = 1: draw crop guess rectangle + * type = 2: draw XYZ image + * type = 3: draw RGB image + * See FragmentType enum below. */ "uniform int type = 0;\n" -"uniform vec4 border_colour;\n" +"uniform vec4 outline_content_colour;\n" +"uniform vec4 crop_guess_colour;\n" "uniform mat4 colour_conversion;\n" "\n" "out vec4 FragColor;\n" @@ -251,9 +261,12 @@ static constexpr char fragment_source[] = "{\n" " switch (type) {\n" " case 0:\n" -" FragColor = border_colour;\n" +" FragColor = outline_content_colour;\n" " break;\n" " case 1:\n" +" FragColor = crop_guess_colour;\n" +" break;\n" +" case 2:\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" @@ -263,13 +276,22 @@ static constexpr char fragment_source[] = " FragColor.y = pow(FragColor.y, OUT_GAMMA);\n" " FragColor.z = pow(FragColor.z, OUT_GAMMA);\n" " break;\n" -" case 2:\n" +" case 3:\n" " FragColor = texture_bicubic(texture_sampler, TexCoord);\n" " break;\n" " }\n" "}\n"; +enum class FragmentType +{ + OUTLINE_CONTENT = 0, + CROP_GUESS = 1, + XYZ_IMAGE = 2, + RGB_IMAGE = 3, +}; + + void GLVideoView::ensure_context () { @@ -284,20 +306,37 @@ 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; +/* Offset and number of indices for the things in the indices array below */ +static constexpr int indices_video_texture_offset = 0; +static constexpr int indices_video_texture_number = 6; +static constexpr int indices_subtitle_texture_offset = indices_video_texture_offset + indices_video_texture_number; +static constexpr int indices_subtitle_texture_number = 6; +static constexpr int indices_outline_content_offset = indices_subtitle_texture_offset + indices_subtitle_texture_number; +static constexpr int indices_outline_content_number = 8; +static constexpr int indices_crop_guess_offset = indices_outline_content_offset + indices_outline_content_number; +static constexpr int indices_crop_guess_number = 8; 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, // outline content line #1 + 9, 10, // outline content line #2 + 10, 11, // outline content line #3 + 11, 8, // outline content line #4 + 12, 13, // crop guess line #1 + 13, 14, // crop guess line #2 + 14, 15, // crop guess line #3 + 15, 12, // crop guess line #4 }; +/* Offsets of things in the GL_ARRAY_BUFFER */ +static constexpr int array_buffer_video_offset = 0; +static constexpr int array_buffer_subtitle_offset = array_buffer_video_offset + 4 * 5 * sizeof(float); +static constexpr int array_buffer_outline_content_offset = array_buffer_subtitle_offset + 4 * 5 * sizeof(float); +static constexpr int array_buffer_crop_guess_offset = array_buffer_outline_content_offset + 4 * 5 * sizeof(float); + void GLVideoView::setup_shaders () @@ -369,10 +408,9 @@ GLVideoView::setup_shaders () 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; + std::vector log_char(log_length); + glGetShaderInfoLog(shader, log_length, nullptr, log_char.data()); + log = string(log_char.data()); } glDeleteShader(shader); throw GLError(String::compose("Could not compile shader (%1)", log).c_str(), -1); @@ -398,10 +436,9 @@ GLVideoView::setup_shaders () 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; + std::vector log_char(log_length); + glGetProgramInfoLog(program, log_length, nullptr, log_char.data()); + log = string(log_char.data()); } glDeleteProgram (program); throw GLError(String::compose("Could not link shader (%1)", log).c_str(), -1); @@ -413,7 +450,8 @@ GLVideoView::setup_shaders () _fragment_type = glGetUniformLocation (program, "type"); check_gl_error ("glGetUniformLocation"); - set_border_colour (program); + set_outline_content_colour (program); + set_crop_guess_colour (program); auto conversion = dcp::ColourConversion::rec709_to_xyz(); boost::numeric::ublas::matrix matrix = conversion.xyz_to_rgb (); @@ -428,14 +466,23 @@ 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, 16 * 5 * sizeof(float), nullptr, GL_STATIC_DRAW); + check_gl_error ("glBufferData"); } void -GLVideoView::set_border_colour (GLuint program) +GLVideoView::set_outline_content_colour (GLuint program) { - auto uniform = glGetUniformLocation (program, "border_colour"); + auto uniform = glGetUniformLocation (program, "outline_content_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); @@ -444,7 +491,18 @@ GLVideoView::set_border_colour (GLuint program) void -GLVideoView::draw (Position, dcp::Size) +GLVideoView::set_crop_guess_colour (GLuint program) +{ + auto uniform = glGetUniformLocation (program, "crop_guess_colour"); + check_gl_error ("glGetUniformLocation"); + auto colour = crop_guess_colour (); + glUniform4f (uniform, colour.Red() / 255.0f, colour.Green() / 255.0f, colour.Blue() / 255.0f, 1.0f); + check_gl_error ("glUniform4f"); +} + + +void +GLVideoView::draw () { auto pad = pad_colour(); glClearColor(pad.Red() / 255.0, pad.Green() / 255.0, pad.Blue() / 255.0, 1.0); @@ -464,11 +522,22 @@ GLVideoView::draw (Position, 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(indices_video_texture)); + glUniform1i(_fragment_type, static_cast(_optimise_for_j2k ? FragmentType::XYZ_IMAGE : FragmentType::RGB_IMAGE)); + _video_texture->bind(); + glDrawElements (GL_TRIANGLES, indices_video_texture_number, GL_UNSIGNED_INT, reinterpret_cast(indices_video_texture_offset * sizeof(int))); + if (_have_subtitle_to_render) { + glUniform1i(_fragment_type, static_cast(FragmentType::RGB_IMAGE)); + _subtitle_texture->bind(); + glDrawElements (GL_TRIANGLES, indices_subtitle_texture_number, GL_UNSIGNED_INT, reinterpret_cast(indices_subtitle_texture_offset * sizeof(int))); + } if (_viewer->outline_content()) { - glUniform1i(_fragment_type, 1); - glDrawElements (GL_LINES, 8, GL_UNSIGNED_INT, reinterpret_cast(indices_border * sizeof(int))); + glUniform1i(_fragment_type, static_cast(FragmentType::OUTLINE_CONTENT)); + glDrawElements (GL_LINES, indices_outline_content_number, GL_UNSIGNED_INT, reinterpret_cast(indices_outline_content_offset * sizeof(int))); + check_gl_error ("glDrawElements"); + } + if (auto guess = _viewer->crop_guess()) { + glUniform1i(_fragment_type, static_cast(FragmentType::CROP_GUESS)); + glDrawElements (GL_LINES, indices_crop_guess_number, GL_UNSIGNED_INT, reinterpret_cast(indices_crop_guess_offset * sizeof(int))); check_gl_error ("glDrawElements"); } @@ -482,77 +551,151 @@ GLVideoView::draw (Position, dcp::Size) void GLVideoView::set_image (shared_ptr 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 video = _optimise_for_j2k ? pv->raw_image() : pv->image(boost::bind(&PlayerVideo::force, 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. - * Othewise we render a RGB image without any shader-side processing. + * Otherwise we render a RGB image without any shader-side processing. */ /* XXX: video range conversion */ - /* XXX: subs */ - if (image->size() != _video_size) { - _have_storage = false; + _video_texture->set (video); + + 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->alignment() == Image::Alignment::COMPACT); + _subtitle_texture->set (text->image); } - _video_size = image->size (); - glPixelStorei (GL_UNPACK_ALIGNMENT, _optimise_for_j2k ? 2 : 1); - check_gl_error ("glPixelStorei"); - auto const format = _optimise_for_j2k ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; + 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 const crop_guess = _viewer->crop_guess(); + + 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); + _last_crop_guess.set_next (crop_guess); + + 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; + } - if (_have_storage) { - glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, _video_size->width, _video_size->height, GL_RGB, format, image->data()[0]); - check_gl_error ("glTexSubImage2D"); - } else { - glTexImage2D (GL_TEXTURE_2D, 0, _optimise_for_j2k ? GL_RGBA12 : GL_RGBA8, _video_size->width, _video_size->height, 0, GL_RGB, format, image->data()[0]); - check_gl_error ("glTexImage2D"); + float const * vertices () const { + return _vertices; + } - auto const canvas_size = _canvas_size.load(); - int const canvas_width = canvas_size.GetWidth(); - int const canvas_height = canvas_size.GetHeight(); + int const size () const { + return sizeof(_vertices); + } - float const image_x = float(_video_size->width) / canvas_width; - float const image_y = float(_video_size->height) / canvas_height; + 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; + } - auto x_pixels_to_gl = [canvas_width](int x) { - return (x * 2.0f / canvas_width) - 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()); + } - auto y_pixels_to_gl = [canvas_height](int y) { - return (y * 2.0f / canvas_height) - 1.0f; - }; + wxSize _canvas_size; + float _vertices[20]; + }; - 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) - }; + auto const sizing_changed = _last_canvas_size.changed() || _last_inter_position.changed() || _last_inter_size.changed() || _last_out_size.changed(); - /* Set the vertex shader's input data (GL_ARRAY_BUFFER) */ - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - check_gl_error ("glBufferData"); + if (sizing_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); - _have_storage = true; + glBufferSubData (GL_ARRAY_BUFFER, array_buffer_video_offset, video.size(), video.vertices()); + check_gl_error ("glBufferSubData (video)"); + + const auto outline_content = Rectangle(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size); + glBufferSubData (GL_ARRAY_BUFFER, array_buffer_outline_content_offset, outline_content.size(), outline_content.vertices()); + check_gl_error ("glBufferSubData (outline_content)"); + } + + if ((sizing_changed || _last_crop_guess.changed()) && crop_guess) { + auto const crop_guess_rectangle = Rectangle( + canvas_size, + inter_position.x + x_offset + inter_size.width * crop_guess->x, + inter_position.y + y_offset + inter_size.height * crop_guess->y, + dcp::Size(inter_size.width * crop_guess->width, inter_size.height * crop_guess->height) + ); + glBufferSubData (GL_ARRAY_BUFFER, array_buffer_crop_guess_offset, crop_guess_rectangle.size(), crop_guess_rectangle.vertices()); + check_gl_error ("glBufferSubData (crop_guess_rectangle)"); + } + + 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, array_buffer_subtitle_offset, 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"); @@ -613,7 +756,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); } } @@ -665,8 +812,8 @@ try _vsync_enabled = true; #endif - _video_texture.reset(new Texture()); - _video_texture->bind(); + _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); @@ -714,7 +861,8 @@ GLVideoView::request_one_shot () } -Texture::Texture () +Texture::Texture (GLint unpack_alignment) + : _unpack_alignment (unpack_alignment) { glGenTextures (1, &_name); check_gl_error ("glGenTextures"); @@ -734,3 +882,56 @@ Texture::bind () 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->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"); + } +} + +#endif