opengl: implement subpictures renderer
authorRomain Vimont <rom1v@videolabs.io>
Tue, 21 Jan 2020 15:04:39 +0000 (16:04 +0100)
committerJean-Baptiste Kempf <jb@videolan.org>
Thu, 13 Feb 2020 22:11:21 +0000 (23:11 +0100)
Extract the subpictures rendering into a separate component, using its
own simplified vertex and fragment shaders.

This simplifies vout_helper.c.

Signed-off-by: Jean-Baptiste Kempf <jb@videolan.org>
modules/video_output/Makefile.am
modules/video_output/opengl/sub_renderer.c [new file with mode: 0644]
modules/video_output/opengl/sub_renderer.h [new file with mode: 0644]
modules/video_output/opengl/vout_helper.c

index 9ba1a9b..55b6e0e 100644 (file)
@@ -9,7 +9,9 @@ OPENGL_COMMONSOURCES = video_output/opengl/vout_helper.c \
        video_output/opengl/interop.h \
        video_output/opengl/vout_helper.h video_output/opengl/converter.h \
        video_output/opengl/internal.h video_output/opengl/fragment_shaders.c \
-       video_output/opengl/interop.c video_output/opengl/interop_sw.c
+       video_output/opengl/interop.c video_output/opengl/interop_sw.c \
+       video_output/opengl/sub_renderer.c \
+       video_output/opengl/sub_renderer.h
 if HAVE_LIBPLACEBO
 OPENGL_COMMONSOURCES += video_output/placebo_utils.c video_output/placebo_utils.h
 endif
diff --git a/modules/video_output/opengl/sub_renderer.c b/modules/video_output/opengl/sub_renderer.c
new file mode 100644 (file)
index 0000000..2bea0b1
--- /dev/null
@@ -0,0 +1,468 @@
+/*****************************************************************************
+ * sub_renderer.c
+ *****************************************************************************
+ * Copyright (C) 2004-2020 VLC authors and VideoLAN
+ * Copyright (C) 2009, 2011 Laurent Aimar
+ *
+ * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
+ *          Ilkka Ollakka <ileoo@videolan.org>
+ *          Rémi Denis-Courmont
+ *          Adrien Maglo <magsoft at videolan dot org>
+ *          Felix Paul Kühne <fkuehne at videolan dot org>
+ *          Pierre d'Herbemont <pdherbemont at videolan dot org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "sub_renderer.h"
+
+#include <assert.h>
+#include <vlc_common.h>
+#include <vlc_es.h>
+#include <vlc_subpicture.h>
+
+#include "gl_util.h"
+#include "interop.h"
+#include "vout_helper.h"
+
+typedef struct {
+    GLuint   texture;
+    GLsizei  width;
+    GLsizei  height;
+
+    float    alpha;
+
+    float    top;
+    float    left;
+    float    bottom;
+    float    right;
+
+    float    tex_width;
+    float    tex_height;
+} gl_region_t;
+
+struct vlc_gl_sub_renderer
+{
+    vlc_gl_t *gl;
+    const opengl_vtable_t *vt;
+
+    struct vlc_gl_interop *interop;
+
+    bool supports_npot;
+    gl_region_t *regions;
+    unsigned region_count;
+
+    GLuint program_id;
+    struct {
+        GLint vertex_pos;
+        GLint tex_coords_in;
+    } aloc;
+    struct {
+        GLint sampler;
+    } uloc;
+
+    GLuint *buffer_objects;
+    unsigned buffer_object_count;
+};
+
+static void
+LogShaderErrors(vlc_object_t *obj, const opengl_vtable_t *vt, GLuint id)
+{
+    GLint info_len;
+    vt->GetShaderiv(id, GL_INFO_LOG_LENGTH, &info_len);
+    if (info_len > 0)
+    {
+        char *info_log = malloc(info_len);
+        if (info_log)
+        {
+            GLsizei written;
+            vt->GetShaderInfoLog(id, info_len, &written, info_log);
+            msg_Err(obj, "shader: %s", info_log);
+            free(info_log);
+        }
+    }
+}
+
+static void
+LogProgramErrors(vlc_object_t *obj, const opengl_vtable_t *vt, GLuint id)
+{
+    GLint info_len;
+    vt->GetProgramiv(id, GL_INFO_LOG_LENGTH, &info_len);
+    if (info_len > 0)
+    {
+        char *info_log = malloc(info_len);
+        if (info_log)
+        {
+            GLsizei written;
+            vt->GetProgramInfoLog(id, info_len, &written, info_log);
+            msg_Err(obj, "program: %s", info_log);
+            free(info_log);
+        }
+    }
+}
+
+static GLuint
+CreateShader(vlc_object_t *obj, const opengl_vtable_t *vt, GLenum type,
+             const char *src)
+{
+    GLuint shader = vt->CreateShader(type);
+    if (!shader)
+        return 0;
+
+    vt->ShaderSource(shader, 1, &src, NULL);
+    vt->CompileShader(shader);
+
+    LogShaderErrors(obj, vt, shader);
+
+    GLint compiled;
+    vt->GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+    if (!compiled)
+    {
+        msg_Err(obj, "Failed to compile shader");
+        vt->DeleteShader(shader);
+        return 0;
+    }
+
+    return shader;
+}
+
+static GLuint
+CreateProgram(vlc_object_t *obj, const opengl_vtable_t *vt)
+{
+    static const char *const VERTEX_SHADER_SRC =
+        "#version 100\n"
+        "attribute vec2 vertex_pos;\n"
+        "attribute vec2 tex_coords_in;\n"
+        "varying vec2 tex_coords;\n"
+        "void main() {\n"
+        "  tex_coords = tex_coords_in;\n"
+        "  gl_Position = vec4(vertex_pos, 0.0, 1.0);\n"
+        "}\n";
+
+    static const char *const FRAGMENT_SHADER_SRC =
+        "#version 100\n"
+        "precision mediump float;\n"
+        "uniform sampler2D sampler;\n"
+        "varying vec2 tex_coords;\n"
+        "void main() {\n"
+        "  gl_FragColor = texture2D(sampler, tex_coords);\n"
+        "}\n";
+
+    GLuint program = 0;
+
+    GLuint vertex_shader = CreateShader(obj, vt, GL_VERTEX_SHADER,
+                                        VERTEX_SHADER_SRC);
+    if (!vertex_shader)
+        return 0;
+
+    GLuint fragment_shader = CreateShader(obj, vt, GL_FRAGMENT_SHADER,
+                                          FRAGMENT_SHADER_SRC);
+    if (!fragment_shader)
+        goto finally_1;
+
+    program = vt->CreateProgram();
+    if (!program)
+        goto finally_2;
+
+    vt->AttachShader(program, vertex_shader);
+    vt->AttachShader(program, fragment_shader);
+
+    vt->LinkProgram(program);
+
+    LogProgramErrors(obj, vt, program);
+
+    GLint linked;
+    vt->GetProgramiv(program, GL_LINK_STATUS, &linked);
+    if (!linked)
+    {
+        msg_Err(obj, "Failed to link program");
+        vt->DeleteProgram(program);
+        program = 0;
+    }
+
+finally_2:
+    vt->DeleteShader(fragment_shader);
+finally_1:
+    vt->DeleteShader(vertex_shader);
+
+    return program;
+}
+
+static int
+FetchLocations(struct vlc_gl_sub_renderer *sr)
+{
+    assert(sr->program_id);
+
+    const opengl_vtable_t *vt = sr->vt;
+
+#define GET_LOC(type, x, str) do { \
+    x = vt->Get##type##Location(sr->program_id, str); \
+    assert(x != -1); \
+    if (x == -1) { \
+        msg_Err(sr->gl, "Unable to Get"#type"Location(%s)", str); \
+        return VLC_EGENERIC; \
+    } \
+} while (0)
+#define GET_ULOC(x, str) GET_LOC(Uniform, x, str)
+#define GET_ALOC(x, str) GET_LOC(Attrib, x, str)
+    GET_ULOC(sr->uloc.sampler, "sampler");
+    GET_ALOC(sr->aloc.vertex_pos, "vertex_pos");
+    GET_ALOC(sr->aloc.tex_coords_in, "tex_coords_in");
+
+#undef GET_LOC
+#undef GET_ULOC
+#undef GET_ALOC
+
+    return VLC_SUCCESS;
+}
+
+struct vlc_gl_sub_renderer *
+vlc_gl_sub_renderer_New(vlc_gl_t *gl, const opengl_vtable_t *vt,
+                        bool supports_npot)
+{
+    struct vlc_gl_sub_renderer *sr = malloc(sizeof(*sr));
+    if (!sr)
+        return NULL;
+
+    video_format_t fmt;
+    video_format_Init(&fmt, VLC_CODEC_RGB32);
+    sr->interop = vlc_gl_interop_New(gl, vt, NULL, &fmt, true);
+    if (!sr->interop)
+        goto error_1;
+
+    /* Allocates our textures */
+    assert(!sr->interop->handle_texs_gen);
+
+    sr->gl = gl;
+    sr->vt = vt;
+    sr->supports_npot = supports_npot;
+    sr->region_count = 0;
+    sr->regions = NULL;
+
+    sr->program_id = CreateProgram(VLC_OBJECT(sr->gl), vt);
+    if (!sr->program_id)
+        goto error_2;
+
+    int ret = FetchLocations(sr);
+    if (ret != VLC_SUCCESS)
+        goto error_3;
+
+    /* Initial number of allocated buffer objects for subpictures, will grow dynamically. */
+    static const unsigned INITIAL_BUFFER_OBJECT_COUNT = 8;
+    sr->buffer_objects = vlc_alloc(INITIAL_BUFFER_OBJECT_COUNT, sizeof(GLuint));
+    if (!sr->buffer_objects)
+        goto error_3;
+
+    sr->buffer_object_count = INITIAL_BUFFER_OBJECT_COUNT;
+
+    vt->GenBuffers(sr->buffer_object_count, sr->buffer_objects);
+
+    return sr;
+
+error_3:
+    vt->DeleteProgram(sr->program_id);
+error_2:
+    vlc_object_delete(sr->interop);
+error_1:
+    free(sr);
+
+    return NULL;
+}
+
+void
+vlc_gl_sub_renderer_Delete(struct vlc_gl_sub_renderer *sr)
+{
+    if (sr->buffer_object_count)
+        sr->vt->DeleteBuffers(sr->buffer_object_count, sr->buffer_objects);
+    free(sr->buffer_objects);
+
+    for (unsigned i = 0; i < sr->region_count; ++i)
+    {
+        if (sr->regions[i].texture)
+            sr->vt->DeleteTextures(1, &sr->regions[i].texture);
+    }
+    free(sr->regions);
+
+    vlc_gl_interop_Delete(sr->interop);
+
+    free(sr);
+}
+
+int
+vlc_gl_sub_renderer_Prepare(struct vlc_gl_sub_renderer *sr, subpicture_t *subpicture)
+{
+    GL_ASSERT_NOERROR();
+
+    const struct vlc_gl_interop *interop = sr->interop;
+
+    int last_count = sr->region_count;
+    gl_region_t *last = sr->regions;
+
+    if (subpicture) {
+        int count = 0;
+        for (subpicture_region_t *r = subpicture->p_region; r; r = r->p_next)
+            count++;
+
+        gl_region_t *regions = calloc(count, sizeof(*regions));
+        if (!regions)
+            return VLC_ENOMEM;
+
+        sr->region_count = count;
+        sr->regions = regions;
+
+        int i = 0;
+        for (subpicture_region_t *r = subpicture->p_region;
+             r; r = r->p_next, i++) {
+            gl_region_t *glr = &sr->regions[i];
+
+            glr->width  = r->fmt.i_visible_width;
+            glr->height = r->fmt.i_visible_height;
+            if (!sr->supports_npot) {
+                glr->width  = vlc_align_pot(glr->width);
+                glr->height = vlc_align_pot(glr->height);
+                glr->tex_width  = (float) r->fmt.i_visible_width  / glr->width;
+                glr->tex_height = (float) r->fmt.i_visible_height / glr->height;
+            } else {
+                glr->tex_width  = 1.0;
+                glr->tex_height = 1.0;
+            }
+            glr->alpha  = (float)subpicture->i_alpha * r->i_alpha / 255 / 255;
+            glr->left   =  2.0 * (r->i_x                          ) / subpicture->i_original_picture_width  - 1.0;
+            glr->top    = -2.0 * (r->i_y                          ) / subpicture->i_original_picture_height + 1.0;
+            glr->right  =  2.0 * (r->i_x + r->fmt.i_visible_width ) / subpicture->i_original_picture_width  - 1.0;
+            glr->bottom = -2.0 * (r->i_y + r->fmt.i_visible_height) / subpicture->i_original_picture_height + 1.0;
+
+            glr->texture = 0;
+            /* Try to recycle the textures allocated by the previous
+               call to this function. */
+            for (int j = 0; j < last_count; j++) {
+                if (last[j].texture &&
+                    last[j].width  == glr->width &&
+                    last[j].height == glr->height) {
+                    glr->texture = last[j].texture;
+                    memset(&last[j], 0, sizeof(last[j]));
+                    break;
+                }
+            }
+
+            const size_t pixels_offset =
+                r->fmt.i_y_offset * r->p_picture->p->i_pitch +
+                r->fmt.i_x_offset * r->p_picture->p->i_pixel_pitch;
+            if (!glr->texture)
+            {
+                /* Could not recycle a previous texture, generate a new one. */
+                int ret = vlc_gl_interop_GenerateTextures(interop, &glr->width,
+                                                          &glr->height,
+                                                          &glr->texture);
+                if (ret != VLC_SUCCESS)
+                    break;
+            }
+            /* Use the visible pitch of the region */
+            r->p_picture->p[0].i_visible_pitch = r->fmt.i_visible_width
+                                               * r->p_picture->p[0].i_pixel_pitch;
+            int ret = interop->ops->update_textures(interop, &glr->texture,
+                                                    &glr->width, &glr->height,
+                                                    r->p_picture, &pixels_offset);
+            if (ret != VLC_SUCCESS)
+                break;
+        }
+    }
+    else
+    {
+        sr->region_count = 0;
+        sr->regions = NULL;
+    }
+
+    for (int i = 0; i < last_count; i++) {
+        if (last[i].texture)
+            vlc_gl_interop_DeleteTextures(interop, &last[i].texture);
+    }
+    free(last);
+
+    GL_ASSERT_NOERROR();
+
+    return VLC_SUCCESS;
+}
+
+int
+vlc_gl_sub_renderer_Draw(struct vlc_gl_sub_renderer *sr)
+{
+    GL_ASSERT_NOERROR();
+
+    const struct vlc_gl_interop *interop = sr->interop;
+    const opengl_vtable_t *vt = sr->vt;
+
+    assert(sr->program_id);
+    vt->UseProgram(sr->program_id);
+
+    vt->Enable(GL_BLEND);
+    vt->BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+    /* We need two buffer objects for each region: for vertex and texture coordinates. */
+    if (2 * sr->region_count > sr->buffer_object_count) {
+        if (sr->buffer_object_count > 0)
+            vt->DeleteBuffers(sr->buffer_object_count, sr->buffer_objects);
+        sr->buffer_object_count = 0;
+
+        int new_count = 2 * sr->region_count;
+        sr->buffer_objects = realloc_or_free(sr->buffer_objects, new_count * sizeof(GLuint));
+        if (!sr->buffer_objects)
+            return VLC_ENOMEM;
+
+        sr->buffer_object_count = new_count;
+        vt->GenBuffers(sr->buffer_object_count, sr->buffer_objects);
+    }
+
+    vt->ActiveTexture(GL_TEXTURE0 + 0);
+    for (unsigned i = 0; i < sr->region_count; i++) {
+        gl_region_t *glr = &sr->regions[i];
+        const GLfloat vertexCoord[] = {
+            glr->left,  glr->top,
+            glr->left,  glr->bottom,
+            glr->right, glr->top,
+            glr->right, glr->bottom,
+        };
+        const GLfloat textureCoord[] = {
+            0.0, 0.0,
+            0.0, glr->tex_height,
+            glr->tex_width, 0.0,
+            glr->tex_width, glr->tex_height,
+        };
+
+        assert(glr->texture != 0);
+        vt->BindTexture(interop->tex_target, glr->texture);
+
+        vt->BindBuffer(GL_ARRAY_BUFFER, sr->buffer_objects[2 * i]);
+        vt->BufferData(GL_ARRAY_BUFFER, sizeof(textureCoord), textureCoord, GL_STATIC_DRAW);
+        vt->EnableVertexAttribArray(sr->aloc.tex_coords_in);
+        vt->VertexAttribPointer(sr->aloc.tex_coords_in, 2, GL_FLOAT, 0, 0, 0);
+
+        vt->BindBuffer(GL_ARRAY_BUFFER, sr->buffer_objects[2 * i + 1]);
+        vt->BufferData(GL_ARRAY_BUFFER, sizeof(vertexCoord), vertexCoord, GL_STATIC_DRAW);
+        vt->EnableVertexAttribArray(sr->aloc.vertex_pos);
+        vt->VertexAttribPointer(sr->aloc.vertex_pos, 2, GL_FLOAT, 0, 0, 0);
+
+        vt->DrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+    }
+    vt->Disable(GL_BLEND);
+
+    GL_ASSERT_NOERROR();
+
+    return VLC_SUCCESS;
+}
diff --git a/modules/video_output/opengl/sub_renderer.h b/modules/video_output/opengl/sub_renderer.h
new file mode 100644 (file)
index 0000000..48c0ebe
--- /dev/null
@@ -0,0 +1,79 @@
+/*****************************************************************************
+ * sub_renderer.h
+ *****************************************************************************
+ * Copyright (C) 2020 Videolabs
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef VLC_SUB_RENDERER_H
+#define VLC_SUB_RENDERER_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_opengl.h>
+
+#include "gl_common.h"
+
+/**
+ * A subpictures renderer handles the rendering of RGB subpictures.
+ */
+struct vlc_gl_sub_renderer;
+
+/**
+ * Create a new subpictures renderer
+ *
+ * \param gl the GL context
+ * \param vt the OpenGL functions vtable
+ * \param supports_npot indicate if the implementation supports non-power-of-2
+ *                      texture size
+ */
+struct vlc_gl_sub_renderer *
+vlc_gl_sub_renderer_New(vlc_gl_t *gl, const opengl_vtable_t *vt,
+                        bool supports_npot);
+
+/**
+ * Delete a subpictures renderer
+ *
+ * \param sr the renderer
+ */
+void
+vlc_gl_sub_renderer_Delete(struct vlc_gl_sub_renderer *sr);
+
+/**
+ * Prepare the fragment shader
+ *
+ * Concretely, it allocates OpenGL textures if necessary and uploads the
+ * picture.
+ *
+ * \param sr the renderer
+ * \param subpicture the subpicture to render
+ */
+int
+vlc_gl_sub_renderer_Prepare(struct vlc_gl_sub_renderer *sr,
+                            subpicture_t *subpicture);
+
+/**
+ * Draw the prepared subpicture
+ *
+ * \param sr the renderer
+ */
+int
+vlc_gl_sub_renderer_Draw(struct vlc_gl_sub_renderer *sr);
+
+#endif
index 3b86a61..3888cda 100644 (file)
 #include "gl_util.h"
 #include "vout_helper.h"
 #include "internal.h"
+#include "sub_renderer.h"
 
 #define SPHERE_RADIUS 1.f
 
-typedef struct {
-    GLuint   texture;
-    GLsizei  width;
-    GLsizei  height;
-
-    float    alpha;
-
-    float    top;
-    float    left;
-    float    bottom;
-    float    right;
-
-    float    tex_width;
-    float    tex_height;
-} gl_region_t;
-
 struct prgm
 {
     GLuint id;
@@ -98,22 +83,13 @@ struct vout_display_opengl_t {
 
     GLuint     texture[PICTURE_PLANE_MAX];
 
-    int         region_count;
-    gl_region_t *region;
-
-    /* One YUV program and one RGBA program (for subpics) */
-    struct prgm prgms[2];
-    struct prgm *prgm; /* Main program */
-    struct prgm *sub_prgm; /* Subpicture program */
+    struct prgm prgm;
 
     unsigned nb_indices;
     GLuint vertex_buffer_object;
     GLuint index_buffer_object;
     GLuint texture_buffer_object[PICTURE_PLANE_MAX];
 
-    GLuint *subpicture_buffer_object;
-    int    subpicture_buffer_object_count;
-
     struct {
         unsigned int i_x_offset;
         unsigned int i_y_offset;
@@ -133,6 +109,8 @@ struct vout_display_opengl_t {
     float f_fovy; /* to avoid recalculating them when needed.      */
     float f_z;    /* Position of the camera on the shpere radius vector */
     float f_sar;
+
+    struct vlc_gl_sub_renderer *sub_renderer;
 };
 
 static const vlc_fourcc_t gl_subpicture_chromas[] = {
@@ -693,35 +671,30 @@ vout_display_opengl_t *vout_display_opengl_New(video_format_t *fmt,
 
     bool b_dump_shaders = var_InheritInteger(gl, "verbose") >= 4;
 
-    vgl->prgm = &vgl->prgms[0];
-    vgl->sub_prgm = &vgl->prgms[1];
-
-    GL_ASSERT_NOERROR();
-    int ret;
-    ret = opengl_init_program(vgl, context, vgl->prgm, fmt, false,
-                              b_dump_shaders);
-    if (ret != VLC_SUCCESS)
+    vgl->sub_renderer =
+        vlc_gl_sub_renderer_New(gl, &vgl->vt, vgl->supports_npot);
+    if (!vgl->sub_renderer)
     {
-        msg_Warn(gl, "could not init tex converter for %4.4s",
-                 (const char *) &fmt->i_chroma);
+        msg_Err(gl, "Could not create sub renderer");
         free(vgl);
         return NULL;
     }
 
     GL_ASSERT_NOERROR();
-    ret = opengl_init_program(vgl, context, vgl->sub_prgm, fmt, true,
-                              b_dump_shaders);
+    int ret = opengl_init_program(vgl, context, &vgl->prgm, fmt, false,
+                                  b_dump_shaders);
     if (ret != VLC_SUCCESS)
     {
-        msg_Warn(gl, "could not init subpictures tex converter for %4.4s",
+        msg_Warn(gl, "could not init tex converter for %4.4s",
                  (const char *) &fmt->i_chroma);
-        opengl_deinit_program(vgl, vgl->prgm);
+        vlc_gl_sub_renderer_Delete(vgl->sub_renderer);
         free(vgl);
         return NULL;
     }
+
     GL_ASSERT_NOERROR();
 
-    const struct vlc_gl_interop *interop = vgl->prgm->tc->interop;
+    const struct vlc_gl_interop *interop = vgl->prgm.tc->interop;
     /* Update the fmt to main program one */
     vgl->fmt = interop->fmt;
     /* The orientation is handled by the orientation matrix */
@@ -742,12 +715,9 @@ vout_display_opengl_t *vout_display_opengl_New(video_format_t *fmt,
         }
     }
 
-    /* Allocates our textures */
-    assert(!vgl->sub_prgm->tc->interop->handle_texs_gen);
-
     if (!interop->handle_texs_gen)
     {
-        ret = vlc_gl_interop_GenerateTextures(vgl->prgm->tc->interop,
+        ret = vlc_gl_interop_GenerateTextures(vgl->prgm.tc->interop,
                                               vgl->tex_width, vgl->tex_height,
                                               vgl->texture);
         if (ret != VLC_SUCCESS)
@@ -769,20 +739,6 @@ vout_display_opengl_t *vout_display_opengl_New(video_format_t *fmt,
     vgl->vt.GenBuffers(1, &vgl->index_buffer_object);
     vgl->vt.GenBuffers(interop->tex_count, vgl->texture_buffer_object);
 
-    /* Initial number of allocated buffer objects for subpictures, will grow dynamically. */
-    int subpicture_buffer_object_count = 8;
-    vgl->subpicture_buffer_object = vlc_alloc(subpicture_buffer_object_count, sizeof(GLuint));
-    if (!vgl->subpicture_buffer_object) {
-        vout_display_opengl_Delete(vgl);
-        return NULL;
-    }
-    vgl->subpicture_buffer_object_count = subpicture_buffer_object_count;
-    vgl->vt.GenBuffers(vgl->subpicture_buffer_object_count, vgl->subpicture_buffer_object);
-
-    /* */
-    vgl->region_count = 0;
-    vgl->region = NULL;
-
     if (vgl->fmt.projection_mode != PROJECTION_MODE_RECTANGULAR
      && vout_display_opengl_SetViewpoint(vgl, viewpoint) != VLC_SUCCESS)
     {
@@ -807,31 +763,21 @@ void vout_display_opengl_Delete(vout_display_opengl_t *vgl)
     vgl->vt.Finish();
     vgl->vt.Flush();
 
-    const struct vlc_gl_interop *interop = vgl->prgm->tc->interop;
+    const struct vlc_gl_interop *interop = vgl->prgm.tc->interop;
     const size_t main_tex_count = interop->tex_count;
     const bool main_del_texs = !interop->handle_texs_gen;
 
-    opengl_deinit_program(vgl, vgl->prgm);
-    opengl_deinit_program(vgl, vgl->sub_prgm);
+    vlc_gl_sub_renderer_Delete(vgl->sub_renderer);
+
+    opengl_deinit_program(vgl, &vgl->prgm);
 
     vgl->vt.DeleteBuffers(1, &vgl->vertex_buffer_object);
     vgl->vt.DeleteBuffers(1, &vgl->index_buffer_object);
     vgl->vt.DeleteBuffers(main_tex_count, vgl->texture_buffer_object);
 
-    if (vgl->subpicture_buffer_object_count > 0)
-        vgl->vt.DeleteBuffers(vgl->subpicture_buffer_object_count,
-                              vgl->subpicture_buffer_object);
-    free(vgl->subpicture_buffer_object);
-
     if (main_del_texs)
         vgl->vt.DeleteTextures(main_tex_count, vgl->texture);
 
-    for (int i = 0; i < vgl->region_count; i++)
-    {
-        if (vgl->region[i].texture)
-            vgl->vt.DeleteTextures(1, &vgl->region[i].texture);
-    }
-    free(vgl->region);
     GL_ASSERT_NOERROR();
 
     free(vgl);
@@ -886,7 +832,7 @@ int vout_display_opengl_SetViewpoint(vout_display_opengl_t *vgl,
         UpdateFOVy(vgl);
         UpdateZ(vgl);
     }
-    getViewpointMatrixes(vgl, vgl->fmt.projection_mode, vgl->prgm);
+    getViewpointMatrixes(vgl, vgl->fmt.projection_mode, &vgl->prgm);
 
     return VLC_SUCCESS;
 }
@@ -901,7 +847,7 @@ void vout_display_opengl_SetWindowAspectRatio(vout_display_opengl_t *vgl,
     vgl->f_sar = f_sar;
     UpdateFOVy(vgl);
     UpdateZ(vgl);
-    getViewpointMatrixes(vgl, vgl->fmt.projection_mode, vgl->prgm);
+    getViewpointMatrixes(vgl, vgl->fmt.projection_mode, &vgl->prgm);
 }
 
 void vout_display_opengl_Viewport(vout_display_opengl_t *vgl, int x, int y,
@@ -910,106 +856,12 @@ void vout_display_opengl_Viewport(vout_display_opengl_t *vgl, int x, int y,
     vgl->vt.Viewport(x, y, width, height);
 }
 
-static int
-vout_display_opengl_PrepareSubPicture(vout_display_opengl_t *vgl,
-                                      subpicture_t *subpicture)
-{
-    GL_ASSERT_NOERROR();
-
-    opengl_tex_converter_t *tc = vgl->sub_prgm->tc;
-    const struct vlc_gl_interop *interop = tc->interop;
-
-    int last_count = vgl->region_count;
-    gl_region_t *last = vgl->region;
-
-    if (subpicture) {
-        int count = 0;
-        for (subpicture_region_t *r = subpicture->p_region; r; r = r->p_next)
-            count++;
-
-        vgl->region_count = count;
-        vgl->region       = calloc(count, sizeof(*vgl->region));
-
-        int i = 0;
-        for (subpicture_region_t *r = subpicture->p_region;
-             r; r = r->p_next, i++) {
-            gl_region_t *glr = &vgl->region[i];
-
-            glr->width  = r->fmt.i_visible_width;
-            glr->height = r->fmt.i_visible_height;
-            if (!vgl->supports_npot) {
-                glr->width  = vlc_align_pot(glr->width);
-                glr->height = vlc_align_pot(glr->height);
-                glr->tex_width  = (float) r->fmt.i_visible_width  / glr->width;
-                glr->tex_height = (float) r->fmt.i_visible_height / glr->height;
-            } else {
-                glr->tex_width  = 1.0;
-                glr->tex_height = 1.0;
-            }
-            glr->alpha  = (float)subpicture->i_alpha * r->i_alpha / 255 / 255;
-            glr->left   =  2.0 * (r->i_x                          ) / subpicture->i_original_picture_width  - 1.0;
-            glr->top    = -2.0 * (r->i_y                          ) / subpicture->i_original_picture_height + 1.0;
-            glr->right  =  2.0 * (r->i_x + r->fmt.i_visible_width ) / subpicture->i_original_picture_width  - 1.0;
-            glr->bottom = -2.0 * (r->i_y + r->fmt.i_visible_height) / subpicture->i_original_picture_height + 1.0;
-
-            glr->texture = 0;
-            /* Try to recycle the textures allocated by the previous
-               call to this function. */
-            for (int j = 0; j < last_count; j++) {
-                if (last[j].texture &&
-                    last[j].width  == glr->width &&
-                    last[j].height == glr->height) {
-                    glr->texture = last[j].texture;
-                    memset(&last[j], 0, sizeof(last[j]));
-                    break;
-                }
-            }
-
-            const size_t pixels_offset =
-                r->fmt.i_y_offset * r->p_picture->p->i_pitch +
-                r->fmt.i_x_offset * r->p_picture->p->i_pixel_pitch;
-            if (!glr->texture)
-            {
-                /* Could not recycle a previous texture, generate a new one. */
-                int ret = vlc_gl_interop_GenerateTextures(interop, &glr->width,
-                                                          &glr->height,
-                                                          &glr->texture);
-                if (ret != VLC_SUCCESS)
-                    break;
-            }
-            /* Use the visible pitch of the region */
-            r->p_picture->p[0].i_visible_pitch = r->fmt.i_visible_width
-                                               * r->p_picture->p[0].i_pixel_pitch;
-            int ret = interop->ops->update_textures(interop, &glr->texture,
-                                                    &glr->width, &glr->height,
-                                                    r->p_picture, &pixels_offset);
-            if (ret != VLC_SUCCESS)
-                break;
-        }
-    }
-    else
-    {
-        vgl->region_count = 0;
-        vgl->region = NULL;
-    }
-
-    for (int i = 0; i < last_count; i++) {
-        if (last[i].texture)
-            vlc_gl_interop_DeleteTextures(interop, &last[i].texture);
-    }
-    free(last);
-
-    GL_ASSERT_NOERROR();
-
-    return VLC_SUCCESS;
-}
-
 int vout_display_opengl_Prepare(vout_display_opengl_t *vgl,
                                 picture_t *picture, subpicture_t *subpicture)
 {
     GL_ASSERT_NOERROR();
 
-    opengl_tex_converter_t *tc = vgl->prgm->tc;
+    opengl_tex_converter_t *tc = vgl->prgm.tc;
     const struct vlc_gl_interop *interop = tc->interop;
 
     /* Update the texture */
@@ -1018,7 +870,7 @@ int vout_display_opengl_Prepare(vout_display_opengl_t *vgl,
     if (ret != VLC_SUCCESS)
         return ret;
 
-    ret = vout_display_opengl_PrepareSubPicture(vgl, subpicture);
+    ret = vlc_gl_sub_renderer_Prepare(vgl->sub_renderer, subpicture);
     GL_ASSERT_NOERROR();
     return ret;
 }
@@ -1295,7 +1147,7 @@ static int SetupCoords(vout_display_opengl_t *vgl,
                        const float *left, const float *top,
                        const float *right, const float *bottom)
 {
-    const struct vlc_gl_interop *interop = vgl->prgm->tc->interop;
+    const struct vlc_gl_interop *interop = vgl->prgm.tc->interop;
 
     GLfloat *vertexCoord, *textureCoord;
     GLushort *indices;
@@ -1423,7 +1275,7 @@ static void TextureCropForStereo(vout_display_opengl_t *vgl,
                                  float *left, float *top,
                                  float *right, float *bottom)
 {
-    const struct vlc_gl_interop *interop = vgl->prgm->tc->interop;
+    const struct vlc_gl_interop *interop = vgl->prgm.tc->interop;
 
     float stereoCoefs[2];
     float stereoOffsets[2];
@@ -1451,91 +1303,6 @@ static void TextureCropForStereo(vout_display_opengl_t *vgl,
     }
 }
 
-static int
-vout_display_opengl_DrawSubPicture(vout_display_opengl_t *vgl)
-{
-    GL_ASSERT_NOERROR();
-
-    struct prgm *prgm = vgl->sub_prgm;
-    GLuint program = prgm->id;
-    opengl_tex_converter_t *tc = prgm->tc;
-    const struct vlc_gl_interop *interop = tc->interop;
-    vgl->vt.UseProgram(program);
-
-    vgl->vt.Enable(GL_BLEND);
-    vgl->vt.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
-    /* We need two buffer objects for each region: for vertex and texture coordinates. */
-    if (2 * vgl->region_count > vgl->subpicture_buffer_object_count) {
-        if (vgl->subpicture_buffer_object_count > 0)
-            vgl->vt.DeleteBuffers(vgl->subpicture_buffer_object_count,
-                                  vgl->subpicture_buffer_object);
-        vgl->subpicture_buffer_object_count = 0;
-
-        int new_count = 2 * vgl->region_count;
-        vgl->subpicture_buffer_object = realloc_or_free(vgl->subpicture_buffer_object, new_count * sizeof(GLuint));
-        if (!vgl->subpicture_buffer_object)
-            return VLC_ENOMEM;
-
-        vgl->subpicture_buffer_object_count = new_count;
-        vgl->vt.GenBuffers(vgl->subpicture_buffer_object_count,
-                           vgl->subpicture_buffer_object);
-    }
-
-    vgl->vt.ActiveTexture(GL_TEXTURE0 + 0);
-    for (int i = 0; i < vgl->region_count; i++) {
-        gl_region_t *glr = &vgl->region[i];
-        const GLfloat vertexCoord[] = {
-            glr->left,  glr->top,
-            glr->left,  glr->bottom,
-            glr->right, glr->top,
-            glr->right, glr->bottom,
-        };
-        const GLfloat textureCoord[] = {
-            0.0, 0.0,
-            0.0, glr->tex_height,
-            glr->tex_width, 0.0,
-            glr->tex_width, glr->tex_height,
-        };
-
-        assert(glr->texture != 0);
-        vgl->vt.BindTexture(interop->tex_target, glr->texture);
-
-        tc->pf_prepare_shader(tc, &glr->width, &glr->height, glr->alpha);
-
-        vgl->vt.BindBuffer(GL_ARRAY_BUFFER, vgl->subpicture_buffer_object[2 * i]);
-        vgl->vt.BufferData(GL_ARRAY_BUFFER, sizeof(textureCoord), textureCoord, GL_STATIC_DRAW);
-        vgl->vt.EnableVertexAttribArray(prgm->aloc.MultiTexCoord[0]);
-        vgl->vt.VertexAttribPointer(prgm->aloc.MultiTexCoord[0], 2, GL_FLOAT,
-                                    0, 0, 0);
-
-        vgl->vt.BindBuffer(GL_ARRAY_BUFFER, vgl->subpicture_buffer_object[2 * i + 1]);
-        vgl->vt.BufferData(GL_ARRAY_BUFFER, sizeof(vertexCoord), vertexCoord, GL_STATIC_DRAW);
-        vgl->vt.EnableVertexAttribArray(prgm->aloc.VertexPosition);
-        vgl->vt.VertexAttribPointer(prgm->aloc.VertexPosition, 2, GL_FLOAT,
-                                    0, 0, 0);
-
-        vgl->vt.UniformMatrix4fv(prgm->uloc.TransformMatrix, 1, GL_FALSE,
-                                 identity);
-
-        vgl->vt.UniformMatrix4fv(prgm->uloc.OrientationMatrix, 1, GL_FALSE,
-                                 prgm->var.OrientationMatrix);
-        vgl->vt.UniformMatrix4fv(prgm->uloc.ProjectionMatrix, 1, GL_FALSE,
-                                 prgm->var.ProjectionMatrix);
-        vgl->vt.UniformMatrix4fv(prgm->uloc.ViewMatrix, 1, GL_FALSE,
-                                 prgm->var.ViewMatrix);
-        vgl->vt.UniformMatrix4fv(prgm->uloc.ZoomMatrix, 1, GL_FALSE,
-                                 prgm->var.ZoomMatrix);
-
-        vgl->vt.DrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-    }
-    vgl->vt.Disable(GL_BLEND);
-
-    GL_ASSERT_NOERROR();
-
-    return VLC_SUCCESS;
-}
-
 int vout_display_opengl_Display(vout_display_opengl_t *vgl,
                                 const video_format_t *source)
 {
@@ -1546,7 +1313,7 @@ int vout_display_opengl_Display(vout_display_opengl_t *vgl,
        Currently, the OS X provider uses it to get a smooth window resizing */
     vgl->vt.Clear(GL_COLOR_BUFFER_BIT);
 
-    vgl->vt.UseProgram(vgl->prgm->id);
+    vgl->vt.UseProgram(vgl->prgm.id);
 
     if (source->i_x_offset != vgl->last_source.i_x_offset
      || source->i_y_offset != vgl->last_source.i_y_offset
@@ -1557,7 +1324,7 @@ int vout_display_opengl_Display(vout_display_opengl_t *vgl,
         float top[PICTURE_PLANE_MAX];
         float right[PICTURE_PLANE_MAX];
         float bottom[PICTURE_PLANE_MAX];
-        const opengl_tex_converter_t *tc = vgl->prgm->tc;
+        const opengl_tex_converter_t *tc = vgl->prgm.tc;
         const struct vlc_gl_interop *interop = tc->interop;
         for (unsigned j = 0; j < interop->tex_count; j++)
         {
@@ -1593,9 +1360,9 @@ int vout_display_opengl_Display(vout_display_opengl_t *vgl,
         vgl->last_source.i_visible_width = source->i_visible_width;
         vgl->last_source.i_visible_height = source->i_visible_height;
     }
-    DrawWithShaders(vgl, vgl->prgm);
+    DrawWithShaders(vgl, &vgl->prgm);
 
-    int ret = vout_display_opengl_DrawSubPicture(vgl);
+    int ret = vlc_gl_sub_renderer_Draw(vgl->sub_renderer);
     if (ret != VLC_SUCCESS)
         return ret;