+static constexpr char vertex_source[] =
+"#version 330 core\n"
+"\n"
+"layout (location = 0) in vec3 in_pos;\n"
+"layout (location = 1) in vec2 in_tex_coord;\n"
+"\n"
+"out vec2 TexCoord;\n"
+"\n"
+"void main()\n"
+"{\n"
+" gl_Position = vec4(in_pos, 1.0);\n"
+" TexCoord = in_tex_coord;\n"
+"}\n";
+
+
+/* Bicubic interpolation stolen from https://stackoverflow.com/questions/13501081/efficient-bicubic-filtering-code-in-glsl */
+static constexpr char fragment_source[] =
+"#version 330 core\n"
+"\n"
+"in vec2 TexCoord;\n"
+"\n"
+"uniform sampler2D texture_sampler;\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"
+" vec4 w;\n"
+" w.x = -x3 + 3 * x2 - 3 * x + 1;\n"
+" w.y = 3 * x3 - 6 * x2 + 4;\n"
+" w.z = -3 * x3 + 3 * x2 + 3 * x + 1;\n"
+" w.w = x3;\n"
+" return w / 6.f;\n"
+"}\n"
+"\n"
+"vec4 texture_bicubic(sampler2D sampler, vec2 tex_coords)\n"
+"{\n"
+" vec2 tex_size = textureSize(sampler, 0);\n"
+" vec2 inv_tex_size = 1.0 / tex_size;\n"
+"\n"
+" tex_coords = tex_coords * tex_size - 0.5;\n"
+"\n"
+" vec2 fxy = fract(tex_coords);\n"
+" tex_coords -= fxy;\n"
+"\n"
+" vec4 xcubic = cubic(fxy.x);\n"
+" vec4 ycubic = cubic(fxy.y);\n"
+"\n"
+" vec4 c = tex_coords.xxyy + vec2 (-0.5, +1.5).xyxy;\n"
+"\n"
+" vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);\n"
+" vec4 offset = c + vec4 (xcubic.yw, ycubic.yw) / s;\n"
+"\n"
+" offset *= inv_tex_size.xxyy;\n"
+"\n"
+" vec4 sample0 = texture(sampler, offset.xz);\n"
+" vec4 sample1 = texture(sampler, offset.yz);\n"
+" vec4 sample2 = texture(sampler, offset.xw);\n"
+" vec4 sample3 = texture(sampler, offset.yw);\n"
+"\n"
+" float sx = s.x / (s.x + s.y);\n"
+" float sy = s.z / (s.z + s.w);\n"
+"\n"
+" return mix(\n"
+" mix(sample3, sample2, sx), mix(sample1, sample0, sx)\n"
+" , sy);\n"
+"}\n"
+"\n"
+"void main()\n"
+"{\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";
+
+
+void
+GLVideoView::ensure_context ()
+{
+ if (!_context) {
+ wxGLContextAttrs attrs;
+ attrs.PlatformDefaults().CoreProfile().OGLVersion(4, 1).EndList();
+ _context = new wxGLContext (_canvas, nullptr, &attrs);
+ if (!_context->IsOK()) {
+ throw GLError ("Making GL context", -1);
+ }
+ }
+}
+
+
+/* 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 ()
+{
+ DCPOMATIC_ASSERT (_canvas);
+ DCPOMATIC_ASSERT (_context);
+ auto r = _canvas->SetCurrent (*_context);
+ DCPOMATIC_ASSERT (r);
+
+#ifdef DCPOMATIC_WINDOWS
+ r = glewInit();
+ if (r != GLEW_OK) {
+ throw GLError(reinterpret_cast<char const*>(glewGetErrorString(r)));
+ }
+#endif
+
+ auto get_information = [this](GLenum name) {
+ auto s = glGetString (name);
+ if (s) {
+ _information[name] = std::string (reinterpret_cast<char const *>(s));
+ }
+ };
+
+ get_information (GL_VENDOR);
+ get_information (GL_RENDERER);
+ get_information (GL_VERSION);
+ get_information (GL_SHADING_LANGUAGE_VERSION);
+
+ glGenVertexArrays(1, &_vao);
+ check_gl_error ("glGenVertexArrays");
+ GLuint vbo;
+ glGenBuffers(1, &vbo);
+ check_gl_error ("glGenBuffers");
+ GLuint ebo;
+ glGenBuffers(1, &ebo);
+ check_gl_error ("glGenBuffers");
+
+ glBindVertexArray(_vao);
+ check_gl_error ("glBindVertexArray");
+
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ check_gl_error ("glBindBuffer");
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
+ check_gl_error ("glBindBuffer");
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+ check_gl_error ("glBufferData");
+
+ /* position attribute to vertex shader (location = 0) */
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), nullptr);
+ glEnableVertexAttribArray(0);
+ /* texture coord attribute to vertex shader (location = 1) */
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), reinterpret_cast<void*>(3 * sizeof(float)));
+ glEnableVertexAttribArray(1);
+ check_gl_error ("glEnableVertexAttribArray");
+
+ auto compile = [](GLenum type, char const* source) -> GLuint {
+ auto shader = glCreateShader(type);
+ DCPOMATIC_ASSERT (shader);
+ GLchar const * src[] = { static_cast<GLchar const *>(source) };
+ glShaderSource(shader, 1, src, nullptr);
+ check_gl_error ("glShaderSource");
+ glCompileShader(shader);
+ check_gl_error ("glCompileShader");
+ GLint ok;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
+ if (!ok) {
+ GLint log_length;
+ 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;
+ }
+ glDeleteShader(shader);
+ throw GLError(String::compose("Could not compile shader (%1)", log).c_str(), -1);
+ }
+ return shader;
+ };
+
+ auto vertex_shader = compile (GL_VERTEX_SHADER, vertex_source);
+ auto fragment_shader = compile (GL_FRAGMENT_SHADER, fragment_source);
+
+ auto program = glCreateProgram();
+ check_gl_error ("glCreateProgram");
+ glAttachShader (program, vertex_shader);
+ check_gl_error ("glAttachShader");
+ glAttachShader (program, fragment_shader);
+ check_gl_error ("glAttachShader");
+ glLinkProgram (program);
+ check_gl_error ("glLinkProgram");
+ GLint ok;
+ glGetProgramiv (program, GL_LINK_STATUS, &ok);
+ if (!ok) {
+ GLint log_length;
+ 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;
+ }
+ glDeleteProgram (program);
+ throw GLError(String::compose("Could not link shader (%1)", log).c_str(), -1);
+ }
+ glDeleteShader (vertex_shader);
+ glDeleteShader (fragment_shader);
+
+ glUseProgram (program);
+
+ _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);