#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"
/* 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
};
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");
}
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);
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");
}
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.
*/
/* 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);
+ }
+
+ 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());
+ }
- /* Set the vertex shader's input data (GL_ARRAY_BUFFER) */
- glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
- check_gl_error ("glBufferData");
+ 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");
auto pv = player_video().first;
if (pv) {
set_image (pv);
- draw (pv->inter_position(), pv->inter_size());
+ }
+
+ draw ();
+
+ if (pv) {
_viewer->image_changed (pv);
}
}
#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);
}
-bool
+void
Texture::set (shared_ptr<const Image> image)
{
auto const create = !_size || image->size() != _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;
+ 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