[vlc-devel] [PATCH v2 28/29] opengl: implement renderer

Romain Vimont rom1v at videolabs.io
Thu Feb 6 14:17:57 CET 2020


Extract the main rendering into a separate component.
---
 modules/video_output/Makefile.am          |    1 +
 modules/video_output/opengl/renderer.c    | 1037 +++++++++++++++++++++
 modules/video_output/opengl/renderer.h    |   53 ++
 modules/video_output/opengl/vout_helper.c |  989 +-------------------
 4 files changed, 1102 insertions(+), 978 deletions(-)
 create mode 100644 modules/video_output/opengl/renderer.c

diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am
index de3b019eb1..566e7b5fd8 100644
--- a/modules/video_output/Makefile.am
+++ b/modules/video_output/Makefile.am
@@ -10,6 +10,7 @@ OPENGL_COMMONSOURCES = video_output/opengl/vout_helper.c \
 	video_output/opengl/vout_helper.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/renderer.c \
 	video_output/opengl/renderer.h \
 	video_output/opengl/sub_renderer.c \
 	video_output/opengl/sub_renderer.h
diff --git a/modules/video_output/opengl/renderer.c b/modules/video_output/opengl/renderer.c
new file mode 100644
index 0000000000..0969c99dc2
--- /dev/null
+++ b/modules/video_output/opengl/renderer.c
@@ -0,0 +1,1037 @@
+/*****************************************************************************
+ * 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 at 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 "renderer.h"
+
+#include <assert.h>
+#include <vlc_common.h>
+#include <vlc_es.h>
+#include <vlc_picture.h>
+
+#include "gl_util.h"
+#include "internal.h"
+#include "interop.h"
+#include "vout_helper.h"
+
+#define SPHERE_RADIUS 1.f
+
+static const GLfloat identity[] = {
+    1.0f, 0.0f, 0.0f, 0.0f,
+    0.0f, 1.0f, 0.0f, 0.0f,
+    0.0f, 0.0f, 1.0f, 0.0f,
+    0.0f, 0.0f, 0.0f, 1.0f
+};
+
+static void getZoomMatrix(float zoom, GLfloat matrix[static 16]) {
+
+    const GLfloat m[] = {
+        /* x   y     z     w */
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, zoom, 1.0f
+    };
+
+    memcpy(matrix, m, sizeof(m));
+}
+
+/* perspective matrix see https://www.opengl.org/sdk/docs/man2/xhtml/gluPerspective.xml */
+static void getProjectionMatrix(float sar, float fovy, GLfloat matrix[static 16]) {
+
+    float zFar  = 1000;
+    float zNear = 0.01;
+
+    float f = 1.f / tanf(fovy / 2.f);
+
+    const GLfloat m[] = {
+        f / sar, 0.f,                   0.f,                0.f,
+        0.f,     f,                     0.f,                0.f,
+        0.f,     0.f,     (zNear + zFar) / (zNear - zFar), -1.f,
+        0.f,     0.f, (2 * zNear * zFar) / (zNear - zFar),  0.f};
+
+     memcpy(matrix, m, sizeof(m));
+}
+
+static void getViewpointMatrixes(struct vlc_gl_renderer *renderer,
+                                 video_projection_mode_t projection_mode)
+{
+    if (projection_mode == PROJECTION_MODE_EQUIRECTANGULAR
+        || projection_mode == PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD)
+    {
+        getProjectionMatrix(renderer->f_sar, renderer->f_fovy,
+                            renderer->var.ProjectionMatrix);
+        getZoomMatrix(renderer->f_z, renderer->var.ZoomMatrix);
+
+        /* renderer->vp has been reversed and is a world transform */
+        vlc_viewpoint_to_4x4(&renderer->vp, renderer->var.ViewMatrix);
+    }
+    else
+    {
+        memcpy(renderer->var.ProjectionMatrix, identity, sizeof(identity));
+        memcpy(renderer->var.ZoomMatrix, identity, sizeof(identity));
+        memcpy(renderer->var.ViewMatrix, identity, sizeof(identity));
+    }
+
+}
+
+static void getOrientationTransformMatrix(video_orientation_t orientation,
+                                          GLfloat matrix[static 16])
+{
+    memcpy(matrix, identity, sizeof(identity));
+
+    const int k_cos_pi = -1;
+    const int k_cos_pi_2 = 0;
+    const int k_cos_n_pi_2 = 0;
+
+    const int k_sin_pi = 0;
+    const int k_sin_pi_2 = 1;
+    const int k_sin_n_pi_2 = -1;
+
+    switch (orientation) {
+
+        case ORIENT_ROTATED_90:
+            matrix[0 * 4 + 0] = k_cos_pi_2;
+            matrix[0 * 4 + 1] = -k_sin_pi_2;
+            matrix[1 * 4 + 0] = k_sin_pi_2;
+            matrix[1 * 4 + 1] = k_cos_pi_2;
+            matrix[3 * 4 + 1] = 1;
+            break;
+        case ORIENT_ROTATED_180:
+            matrix[0 * 4 + 0] = k_cos_pi;
+            matrix[0 * 4 + 1] = -k_sin_pi;
+            matrix[1 * 4 + 0] = k_sin_pi;
+            matrix[1 * 4 + 1] = k_cos_pi;
+            matrix[3 * 4 + 0] = 1;
+            matrix[3 * 4 + 1] = 1;
+            break;
+        case ORIENT_ROTATED_270:
+            matrix[0 * 4 + 0] = k_cos_n_pi_2;
+            matrix[0 * 4 + 1] = -k_sin_n_pi_2;
+            matrix[1 * 4 + 0] = k_sin_n_pi_2;
+            matrix[1 * 4 + 1] = k_cos_n_pi_2;
+            matrix[3 * 4 + 0] = 1;
+            break;
+        case ORIENT_HFLIPPED:
+            matrix[0 * 4 + 0] = -1;
+            matrix[3 * 4 + 0] = 1;
+            break;
+        case ORIENT_VFLIPPED:
+            matrix[1 * 4 + 1] = -1;
+            matrix[3 * 4 + 1] = 1;
+            break;
+        case ORIENT_TRANSPOSED:
+            matrix[0 * 4 + 0] = 0;
+            matrix[1 * 4 + 1] = 0;
+            matrix[2 * 4 + 2] = -1;
+            matrix[0 * 4 + 1] = 1;
+            matrix[1 * 4 + 0] = 1;
+            break;
+        case ORIENT_ANTI_TRANSPOSED:
+            matrix[0 * 4 + 0] = 0;
+            matrix[1 * 4 + 1] = 0;
+            matrix[2 * 4 + 2] = -1;
+            matrix[0 * 4 + 1] = -1;
+            matrix[1 * 4 + 0] = -1;
+            matrix[3 * 4 + 0] = 1;
+            matrix[3 * 4 + 1] = 1;
+            break;
+        default:
+            break;
+    }
+}
+
+static GLuint BuildVertexShader(const struct vlc_gl_renderer *renderer,
+                                unsigned plane_count)
+{
+    const opengl_vtable_t *vt = renderer->vt;
+
+    /* Basic vertex shader */
+    static const char *template =
+        "#version %u\n"
+        "varying vec2 TexCoord0;\n"
+        "attribute vec4 MultiTexCoord0;\n"
+        "%s%s"
+        "attribute vec3 VertexPosition;\n"
+        "uniform mat4 TransformMatrix;\n"
+        "uniform mat4 OrientationMatrix;\n"
+        "uniform mat4 ProjectionMatrix;\n"
+        "uniform mat4 ZoomMatrix;\n"
+        "uniform mat4 ViewMatrix;\n"
+        "void main() {\n"
+        " TexCoord0 = vec4(TransformMatrix * OrientationMatrix * MultiTexCoord0).st;\n"
+        "%s%s"
+        " gl_Position = ProjectionMatrix * ZoomMatrix * ViewMatrix\n"
+        "               * vec4(VertexPosition, 1.0);\n"
+        "}";
+
+    const char *coord1_header = plane_count > 1 ?
+        "varying vec2 TexCoord1;\nattribute vec4 MultiTexCoord1;\n" : "";
+    const char *coord1_code = plane_count > 1 ?
+        " TexCoord1 = vec4(TransformMatrix * OrientationMatrix * MultiTexCoord1).st;\n" : "";
+    const char *coord2_header = plane_count > 2 ?
+        "varying vec2 TexCoord2;\nattribute vec4 MultiTexCoord2;\n" : "";
+    const char *coord2_code = plane_count > 2 ?
+        " TexCoord2 = vec4(TransformMatrix * OrientationMatrix * MultiTexCoord2).st;\n" : "";
+
+    char *code;
+    if (asprintf(&code, template, renderer->glsl_version, coord1_header,
+                 coord2_header, coord1_code, coord2_code) < 0)
+        return 0;
+
+    GLuint shader = vt->CreateShader(GL_VERTEX_SHADER);
+    vt->ShaderSource(shader, 1, (const char **) &code, NULL);
+    if (renderer->b_dump_shaders)
+        msg_Dbg(renderer->gl, "\n=== Vertex shader for fourcc: %4.4s ===\n%s\n",
+                (const char *) &renderer->interop->fmt.i_chroma, code);
+    vt->CompileShader(shader);
+    free(code);
+    return shader;
+}
+
+static int
+opengl_link_program(struct vlc_gl_renderer *renderer)
+{
+    struct vlc_gl_interop *interop = renderer->interop;
+    const opengl_vtable_t *vt = renderer->vt;
+
+    GLuint vertex_shader = BuildVertexShader(renderer, interop->tex_count);
+    if (!vertex_shader)
+        return VLC_EGENERIC;
+
+    GLuint fragment_shader =
+        opengl_fragment_shader_init(renderer, interop->tex_target,
+                                    interop->sw_fmt.i_chroma,
+                                    interop->sw_fmt.space);
+    if (!fragment_shader)
+        return VLC_EGENERIC;
+
+    assert(interop->tex_target != 0 &&
+           interop->tex_count > 0 &&
+           interop->ops->update_textures != NULL &&
+           renderer->pf_fetch_locations != NULL &&
+           renderer->pf_prepare_shader != NULL);
+
+    GLuint shaders[] = { fragment_shader, vertex_shader };
+
+    /* Check shaders messages */
+    for (unsigned i = 0; i < 2; i++) {
+        int infoLength;
+        vt->GetShaderiv(shaders[i], GL_INFO_LOG_LENGTH, &infoLength);
+        if (infoLength <= 1)
+            continue;
+
+        char *infolog = malloc(infoLength);
+        if (infolog != NULL)
+        {
+            int charsWritten;
+            vt->GetShaderInfoLog(shaders[i], infoLength, &charsWritten,
+                                 infolog);
+            msg_Err(renderer->gl, "shader %u: %s", i, infolog);
+            free(infolog);
+        }
+    }
+
+    GLuint program_id = renderer->program_id = vt->CreateProgram();
+    vt->AttachShader(program_id, fragment_shader);
+    vt->AttachShader(program_id, vertex_shader);
+    vt->LinkProgram(program_id);
+
+    vt->DeleteShader(vertex_shader);
+    vt->DeleteShader(fragment_shader);
+
+    /* Check program messages */
+    int infoLength = 0;
+    vt->GetProgramiv(program_id, GL_INFO_LOG_LENGTH, &infoLength);
+    if (infoLength > 1)
+    {
+        char *infolog = malloc(infoLength);
+        if (infolog != NULL)
+        {
+            int charsWritten;
+            vt->GetProgramInfoLog(program_id, infoLength, &charsWritten,
+                                  infolog);
+            msg_Err(renderer->gl, "shader program: %s", infolog);
+            free(infolog);
+        }
+
+        /* If there is some message, better to check linking is ok */
+        GLint link_status = GL_TRUE;
+        vt->GetProgramiv(program_id, GL_LINK_STATUS, &link_status);
+        if (link_status == GL_FALSE)
+        {
+            msg_Err(renderer->gl, "Unable to use program");
+            goto error;
+        }
+    }
+
+    /* Fetch UniformLocations and AttribLocations */
+#define GET_LOC(type, x, str) do { \
+    x = vt->Get##type##Location(program_id, str); \
+    assert(x != -1); \
+    if (x == -1) { \
+        msg_Err(renderer->gl, "Unable to Get"#type"Location(%s)", str); \
+        goto error; \
+    } \
+} while (0)
+#define GET_ULOC(x, str) GET_LOC(Uniform, renderer->uloc.x, str)
+#define GET_ALOC(x, str) GET_LOC(Attrib, renderer->aloc.x, str)
+    GET_ULOC(TransformMatrix, "TransformMatrix");
+    GET_ULOC(OrientationMatrix, "OrientationMatrix");
+    GET_ULOC(ProjectionMatrix, "ProjectionMatrix");
+    GET_ULOC(ViewMatrix, "ViewMatrix");
+    GET_ULOC(ZoomMatrix, "ZoomMatrix");
+
+    GET_ALOC(VertexPosition, "VertexPosition");
+    GET_ALOC(MultiTexCoord[0], "MultiTexCoord0");
+    /* MultiTexCoord 1 and 2 can be optimized out if not used */
+    if (interop->tex_count > 1)
+        GET_ALOC(MultiTexCoord[1], "MultiTexCoord1");
+    else
+        renderer->aloc.MultiTexCoord[1] = -1;
+    if (interop->tex_count > 2)
+        GET_ALOC(MultiTexCoord[2], "MultiTexCoord2");
+    else
+        renderer->aloc.MultiTexCoord[2] = -1;
+#undef GET_LOC
+#undef GET_ULOC
+#undef GET_ALOC
+    int ret = renderer->pf_fetch_locations(renderer, program_id);
+    assert(ret == VLC_SUCCESS);
+    if (ret != VLC_SUCCESS)
+    {
+        msg_Err(renderer->gl, "Unable to get locations from tex_conv");
+        goto error;
+    }
+
+    return VLC_SUCCESS;
+
+error:
+    vt->DeleteProgram(program_id);
+    renderer->program_id = 0;
+    return VLC_EGENERIC;
+}
+
+void
+vlc_gl_renderer_Delete(struct vlc_gl_renderer *renderer)
+{
+    struct vlc_gl_interop *interop = renderer->interop;
+    const opengl_vtable_t *vt = renderer->vt;
+
+    vt->DeleteBuffers(1, &renderer->vertex_buffer_object);
+    vt->DeleteBuffers(1, &renderer->index_buffer_object);
+    vt->DeleteBuffers(interop->tex_count, renderer->texture_buffer_object);
+
+    if (!interop->handle_texs_gen)
+        vt->DeleteTextures(interop->tex_count, renderer->textures);
+
+    vlc_gl_interop_Delete(interop);
+    if (renderer->program_id != 0)
+        renderer->vt->DeleteProgram(renderer->program_id);
+
+#ifdef HAVE_LIBPLACEBO
+    FREENULL(renderer->uloc.pl_vars);
+    if (renderer->pl_ctx)
+        pl_context_destroy(&renderer->pl_ctx);
+#endif
+
+    free(renderer);
+}
+
+struct vlc_gl_renderer *
+vlc_gl_renderer_New(vlc_gl_t *gl, const opengl_vtable_t *vt,
+                    vlc_video_context *context, const video_format_t *fmt,
+                    bool supports_npot, bool b_dump_shaders)
+{
+    struct vlc_gl_renderer *renderer = calloc(1, sizeof(*renderer));
+    if (!renderer)
+        return NULL;
+
+    struct vlc_gl_interop *interop =
+        vlc_gl_interop_New(gl, vt, context, fmt, false);
+    if (!interop)
+    {
+        free(renderer);
+        return NULL;
+    }
+
+    renderer->interop = interop;
+
+    renderer->gl = gl;
+    renderer->vt = vt;
+    renderer->b_dump_shaders = b_dump_shaders;
+#if defined(USE_OPENGL_ES2)
+    renderer->glsl_version = 100;
+    renderer->glsl_precision_header = "precision highp float;\n";
+#else
+    renderer->glsl_version = 120;
+    renderer->glsl_precision_header = "";
+#endif
+
+#ifdef HAVE_LIBPLACEBO
+    // Create the main libplacebo context
+    renderer->pl_ctx = vlc_placebo_Create(VLC_OBJECT(gl));
+    if (renderer->pl_ctx) {
+#   if PL_API_VER >= 20
+        renderer->pl_sh = pl_shader_alloc(renderer->pl_ctx, NULL);
+#   elif PL_API_VER >= 6
+        renderer->pl_sh = pl_shader_alloc(renderer->pl_ctx, NULL, 0);
+#   else
+        renderer->pl_sh = pl_shader_alloc(renderer->pl_ctx, NULL, 0, 0);
+#   endif
+    }
+#endif
+
+    int ret = opengl_link_program(renderer);
+    if (ret != VLC_SUCCESS)
+    {
+        vlc_gl_renderer_Delete(renderer);
+        return NULL;
+    }
+
+    getOrientationTransformMatrix(interop->fmt.orientation,
+                                  renderer->var.OrientationMatrix);
+    getViewpointMatrixes(renderer, interop->fmt.projection_mode);
+
+    /* Update the fmt to main program one */
+    renderer->fmt = interop->fmt;
+    /* The orientation is handled by the orientation matrix */
+    renderer->fmt.orientation = fmt->orientation;
+
+    /* Texture size */
+    for (unsigned j = 0; j < interop->tex_count; j++) {
+        const GLsizei w = renderer->fmt.i_visible_width  * interop->texs[j].w.num
+                        / interop->texs[j].w.den;
+        const GLsizei h = renderer->fmt.i_visible_height * interop->texs[j].h.num
+                        / interop->texs[j].h.den;
+        if (supports_npot) {
+            renderer->tex_width[j]  = w;
+            renderer->tex_height[j] = h;
+        } else {
+            renderer->tex_width[j]  = vlc_align_pot(w);
+            renderer->tex_height[j] = vlc_align_pot(h);
+        }
+    }
+
+    if (!interop->handle_texs_gen)
+    {
+        ret = vlc_gl_interop_GenerateTextures(interop, renderer->tex_width,
+                                              renderer->tex_height,
+                                              renderer->textures);
+        if (ret != VLC_SUCCESS)
+        {
+            vlc_gl_renderer_Delete(renderer);
+            return NULL;
+        }
+    }
+
+    /* */
+    vt->Disable(GL_BLEND);
+    vt->Disable(GL_DEPTH_TEST);
+    vt->DepthMask(GL_FALSE);
+    vt->Enable(GL_CULL_FACE);
+    vt->ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+    vt->Clear(GL_COLOR_BUFFER_BIT);
+
+    vt->GenBuffers(1, &renderer->vertex_buffer_object);
+    vt->GenBuffers(1, &renderer->index_buffer_object);
+    vt->GenBuffers(interop->tex_count, renderer->texture_buffer_object);
+
+    return renderer;
+}
+
+static void UpdateZ(struct vlc_gl_renderer *renderer)
+{
+    /* Do trigonometry to calculate the minimal z value
+     * that will allow us to zoom out without seeing the outside of the
+     * sphere (black borders). */
+    float tan_fovx_2 = tanf(renderer->f_fovx / 2);
+    float tan_fovy_2 = tanf(renderer->f_fovy / 2);
+    float z_min = - SPHERE_RADIUS / sinf(atanf(sqrtf(
+                    tan_fovx_2 * tan_fovx_2 + tan_fovy_2 * tan_fovy_2)));
+
+    /* The FOV value above which z is dynamically calculated. */
+    const float z_thresh = 90.f;
+
+    if (renderer->f_fovx <= z_thresh * M_PI / 180)
+        renderer->f_z = 0;
+    else
+    {
+        float f = z_min / ((FIELD_OF_VIEW_DEGREES_MAX - z_thresh) * M_PI / 180);
+        renderer->f_z = f * renderer->f_fovx - f * z_thresh * M_PI / 180;
+        if (renderer->f_z < z_min)
+            renderer->f_z = z_min;
+    }
+}
+
+static void UpdateFOVy(struct vlc_gl_renderer *renderer)
+{
+    renderer->f_fovy = 2 * atanf(tanf(renderer->f_fovx / 2) / renderer->f_sar);
+}
+
+int
+vlc_gl_renderer_SetViewpoint(struct vlc_gl_renderer *renderer,
+                             const vlc_viewpoint_t *p_vp)
+{
+    if (p_vp->fov > FIELD_OF_VIEW_DEGREES_MAX
+            || p_vp->fov < FIELD_OF_VIEW_DEGREES_MIN)
+        return VLC_EBADVAR;
+
+    // Convert degree into radian
+    float f_fovx = p_vp->fov * (float)M_PI / 180.f;
+
+    /* vgl->vp needs to be converted into world transform */
+    vlc_viewpoint_reverse(&renderer->vp, p_vp);
+
+    if (fabsf(f_fovx - renderer->f_fovx) >= 0.001f)
+    {
+        /* FOVx has changed. */
+        renderer->f_fovx = f_fovx;
+        UpdateFOVy(renderer);
+        UpdateZ(renderer);
+    }
+    getViewpointMatrixes(renderer, renderer->fmt.projection_mode);
+
+    return VLC_SUCCESS;
+}
+
+void
+vlc_gl_renderer_SetWindowAspectRatio(struct vlc_gl_renderer *renderer,
+                                     float f_sar)
+{
+    /* Each time the window size changes, we must recompute the minimum zoom
+     * since the aspect ration changes.
+     * We must also set the new current zoom value. */
+    renderer->f_sar = f_sar;
+    UpdateFOVy(renderer);
+    UpdateZ(renderer);
+    getViewpointMatrixes(renderer, renderer->fmt.projection_mode);
+}
+
+int
+vlc_gl_renderer_Prepare(struct vlc_gl_renderer *renderer, picture_t *picture)
+{
+    const struct vlc_gl_interop *interop = renderer->interop;
+    /* Update the texture */
+    return interop->ops->update_textures(interop, renderer->textures,
+                                         renderer->tex_width,
+                                         renderer->tex_height, picture,
+                                         NULL);
+}
+
+static int BuildSphere(unsigned nbPlanes,
+                        GLfloat **vertexCoord, GLfloat **textureCoord, unsigned *nbVertices,
+                        GLushort **indices, unsigned *nbIndices,
+                        const float *left, const float *top,
+                        const float *right, const float *bottom)
+{
+    unsigned nbLatBands = 128;
+    unsigned nbLonBands = 128;
+
+    *nbVertices = (nbLatBands + 1) * (nbLonBands + 1);
+    *nbIndices = nbLatBands * nbLonBands * 3 * 2;
+
+    *vertexCoord = vlc_alloc(*nbVertices * 3, sizeof(GLfloat));
+    if (*vertexCoord == NULL)
+        return VLC_ENOMEM;
+    *textureCoord = vlc_alloc(nbPlanes * *nbVertices * 2, sizeof(GLfloat));
+    if (*textureCoord == NULL)
+    {
+        free(*vertexCoord);
+        return VLC_ENOMEM;
+    }
+    *indices = vlc_alloc(*nbIndices, sizeof(GLushort));
+    if (*indices == NULL)
+    {
+        free(*textureCoord);
+        free(*vertexCoord);
+        return VLC_ENOMEM;
+    }
+
+    for (unsigned lat = 0; lat <= nbLatBands; lat++) {
+        float theta = lat * (float) M_PI / nbLatBands;
+        float sinTheta, cosTheta;
+
+        sincosf(theta, &sinTheta, &cosTheta);
+
+        for (unsigned lon = 0; lon <= nbLonBands; lon++) {
+            float phi = lon * 2 * (float) M_PI / nbLonBands;
+            float sinPhi, cosPhi;
+
+            sincosf(phi, &sinPhi, &cosPhi);
+
+            float x = cosPhi * sinTheta;
+            float y = cosTheta;
+            float z = sinPhi * sinTheta;
+
+            unsigned off1 = (lat * (nbLonBands + 1) + lon) * 3;
+            (*vertexCoord)[off1] = SPHERE_RADIUS * x;
+            (*vertexCoord)[off1 + 1] = SPHERE_RADIUS * y;
+            (*vertexCoord)[off1 + 2] = SPHERE_RADIUS * z;
+
+            for (unsigned p = 0; p < nbPlanes; ++p)
+            {
+                unsigned off2 = (p * (nbLatBands + 1) * (nbLonBands + 1)
+                                + lat * (nbLonBands + 1) + lon) * 2;
+                float width = right[p] - left[p];
+                float height = bottom[p] - top[p];
+                float u = (float)lon / nbLonBands * width;
+                float v = (float)lat / nbLatBands * height;
+                (*textureCoord)[off2] = u;
+                (*textureCoord)[off2 + 1] = v;
+            }
+        }
+    }
+
+    for (unsigned lat = 0; lat < nbLatBands; lat++) {
+        for (unsigned lon = 0; lon < nbLonBands; lon++) {
+            unsigned first = (lat * (nbLonBands + 1)) + lon;
+            unsigned second = first + nbLonBands + 1;
+
+            unsigned off = (lat * nbLatBands + lon) * 3 * 2;
+
+            (*indices)[off] = first;
+            (*indices)[off + 1] = second;
+            (*indices)[off + 2] = first + 1;
+
+            (*indices)[off + 3] = second;
+            (*indices)[off + 4] = second + 1;
+            (*indices)[off + 5] = first + 1;
+        }
+    }
+
+    return VLC_SUCCESS;
+}
+
+
+static int BuildCube(unsigned nbPlanes,
+                     float padW, float padH,
+                     GLfloat **vertexCoord, GLfloat **textureCoord, unsigned *nbVertices,
+                     GLushort **indices, unsigned *nbIndices,
+                     const float *left, const float *top,
+                     const float *right, const float *bottom)
+{
+    *nbVertices = 4 * 6;
+    *nbIndices = 6 * 6;
+
+    *vertexCoord = vlc_alloc(*nbVertices * 3, sizeof(GLfloat));
+    if (*vertexCoord == NULL)
+        return VLC_ENOMEM;
+    *textureCoord = vlc_alloc(nbPlanes * *nbVertices * 2, sizeof(GLfloat));
+    if (*textureCoord == NULL)
+    {
+        free(*vertexCoord);
+        return VLC_ENOMEM;
+    }
+    *indices = vlc_alloc(*nbIndices, sizeof(GLushort));
+    if (*indices == NULL)
+    {
+        free(*textureCoord);
+        free(*vertexCoord);
+        return VLC_ENOMEM;
+    }
+
+    static const GLfloat coord[] = {
+        -1.0,    1.0,    -1.0f, // front
+        -1.0,    -1.0,   -1.0f,
+        1.0,     1.0,    -1.0f,
+        1.0,     -1.0,   -1.0f,
+
+        -1.0,    1.0,    1.0f, // back
+        -1.0,    -1.0,   1.0f,
+        1.0,     1.0,    1.0f,
+        1.0,     -1.0,   1.0f,
+
+        -1.0,    1.0,    -1.0f, // left
+        -1.0,    -1.0,   -1.0f,
+        -1.0,     1.0,    1.0f,
+        -1.0,     -1.0,   1.0f,
+
+        1.0f,    1.0,    -1.0f, // right
+        1.0f,   -1.0,    -1.0f,
+        1.0f,   1.0,     1.0f,
+        1.0f,   -1.0,    1.0f,
+
+        -1.0,    -1.0,    1.0f, // bottom
+        -1.0,    -1.0,   -1.0f,
+        1.0,     -1.0,    1.0f,
+        1.0,     -1.0,   -1.0f,
+
+        -1.0,    1.0,    1.0f, // top
+        -1.0,    1.0,   -1.0f,
+        1.0,     1.0,    1.0f,
+        1.0,     1.0,   -1.0f,
+    };
+
+    memcpy(*vertexCoord, coord, *nbVertices * 3 * sizeof(GLfloat));
+
+    for (unsigned p = 0; p < nbPlanes; ++p)
+    {
+        float width = right[p] - left[p];
+        float height = bottom[p] - top[p];
+
+        float col[] = {left[p],
+                       left[p] + width * 1.f/3,
+                       left[p] + width * 2.f/3,
+                       left[p] + width};
+
+        float row[] = {top[p],
+                       top[p] + height * 1.f/2,
+                       top[p] + height};
+
+        const GLfloat tex[] = {
+            col[1] + padW, row[1] + padH, // front
+            col[1] + padW, row[2] - padH,
+            col[2] - padW, row[1] + padH,
+            col[2] - padW, row[2] - padH,
+
+            col[3] - padW, row[1] + padH, // back
+            col[3] - padW, row[2] - padH,
+            col[2] + padW, row[1] + padH,
+            col[2] + padW, row[2] - padH,
+
+            col[2] - padW, row[0] + padH, // left
+            col[2] - padW, row[1] - padH,
+            col[1] + padW, row[0] + padH,
+            col[1] + padW, row[1] - padH,
+
+            col[0] + padW, row[0] + padH, // right
+            col[0] + padW, row[1] - padH,
+            col[1] - padW, row[0] + padH,
+            col[1] - padW, row[1] - padH,
+
+            col[0] + padW, row[2] - padH, // bottom
+            col[0] + padW, row[1] + padH,
+            col[1] - padW, row[2] - padH,
+            col[1] - padW, row[1] + padH,
+
+            col[2] + padW, row[0] + padH, // top
+            col[2] + padW, row[1] - padH,
+            col[3] - padW, row[0] + padH,
+            col[3] - padW, row[1] - padH,
+        };
+
+        memcpy(*textureCoord + p * *nbVertices * 2, tex,
+               *nbVertices * 2 * sizeof(GLfloat));
+    }
+
+    const GLushort ind[] = {
+        0, 1, 2,       2, 1, 3, // front
+        6, 7, 4,       4, 7, 5, // back
+        10, 11, 8,     8, 11, 9, // left
+        12, 13, 14,    14, 13, 15, // right
+        18, 19, 16,    16, 19, 17, // bottom
+        20, 21, 22,    22, 21, 23, // top
+    };
+
+    memcpy(*indices, ind, *nbIndices * sizeof(GLushort));
+
+    return VLC_SUCCESS;
+}
+
+static int BuildRectangle(unsigned nbPlanes,
+                          GLfloat **vertexCoord, GLfloat **textureCoord, unsigned *nbVertices,
+                          GLushort **indices, unsigned *nbIndices,
+                          const float *left, const float *top,
+                          const float *right, const float *bottom)
+{
+    *nbVertices = 4;
+    *nbIndices = 6;
+
+    *vertexCoord = vlc_alloc(*nbVertices * 3, sizeof(GLfloat));
+    if (*vertexCoord == NULL)
+        return VLC_ENOMEM;
+    *textureCoord = vlc_alloc(nbPlanes * *nbVertices * 2, sizeof(GLfloat));
+    if (*textureCoord == NULL)
+    {
+        free(*vertexCoord);
+        return VLC_ENOMEM;
+    }
+    *indices = vlc_alloc(*nbIndices, sizeof(GLushort));
+    if (*indices == NULL)
+    {
+        free(*textureCoord);
+        free(*vertexCoord);
+        return VLC_ENOMEM;
+    }
+
+    static const GLfloat coord[] = {
+       -1.0,    1.0,    -1.0f,
+       -1.0,    -1.0,   -1.0f,
+       1.0,     1.0,    -1.0f,
+       1.0,     -1.0,   -1.0f
+    };
+
+    memcpy(*vertexCoord, coord, *nbVertices * 3 * sizeof(GLfloat));
+
+    for (unsigned p = 0; p < nbPlanes; ++p)
+    {
+        const GLfloat tex[] = {
+            left[p],  top[p],
+            left[p],  bottom[p],
+            right[p], top[p],
+            right[p], bottom[p]
+        };
+
+        memcpy(*textureCoord + p * *nbVertices * 2, tex,
+               *nbVertices * 2 * sizeof(GLfloat));
+    }
+
+    const GLushort ind[] = {
+        0, 1, 2,
+        2, 1, 3
+    };
+
+    memcpy(*indices, ind, *nbIndices * sizeof(GLushort));
+
+    return VLC_SUCCESS;
+}
+
+static int SetupCoords(struct vlc_gl_renderer *renderer,
+                       const float *left, const float *top,
+                       const float *right, const float *bottom)
+{
+    const struct vlc_gl_interop *interop = renderer->interop;
+    const opengl_vtable_t *vt = renderer->vt;
+
+    GLfloat *vertexCoord, *textureCoord;
+    GLushort *indices;
+    unsigned nbVertices, nbIndices;
+
+    int i_ret;
+    switch (renderer->fmt.projection_mode)
+    {
+    case PROJECTION_MODE_RECTANGULAR:
+        i_ret = BuildRectangle(interop->tex_count,
+                               &vertexCoord, &textureCoord, &nbVertices,
+                               &indices, &nbIndices,
+                               left, top, right, bottom);
+        break;
+    case PROJECTION_MODE_EQUIRECTANGULAR:
+        i_ret = BuildSphere(interop->tex_count,
+                            &vertexCoord, &textureCoord, &nbVertices,
+                            &indices, &nbIndices,
+                            left, top, right, bottom);
+        break;
+    case PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD:
+        i_ret = BuildCube(interop->tex_count,
+                          (float)renderer->fmt.i_cubemap_padding / renderer->fmt.i_width,
+                          (float)renderer->fmt.i_cubemap_padding / renderer->fmt.i_height,
+                          &vertexCoord, &textureCoord, &nbVertices,
+                          &indices, &nbIndices,
+                          left, top, right, bottom);
+        break;
+    default:
+        i_ret = VLC_EGENERIC;
+        break;
+    }
+
+    if (i_ret != VLC_SUCCESS)
+        return i_ret;
+
+    for (unsigned j = 0; j < interop->tex_count; j++)
+    {
+        vt->BindBuffer(GL_ARRAY_BUFFER, renderer->texture_buffer_object[j]);
+        vt->BufferData(GL_ARRAY_BUFFER, nbVertices * 2 * sizeof(GLfloat),
+                       textureCoord + j * nbVertices * 2, GL_STATIC_DRAW);
+    }
+
+    vt->BindBuffer(GL_ARRAY_BUFFER, renderer->vertex_buffer_object);
+    vt->BufferData(GL_ARRAY_BUFFER, nbVertices * 3 * sizeof(GLfloat),
+                   vertexCoord, GL_STATIC_DRAW);
+
+    vt->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->index_buffer_object);
+    vt->BufferData(GL_ELEMENT_ARRAY_BUFFER, nbIndices * sizeof(GLushort),
+                   indices, GL_STATIC_DRAW);
+
+    free(textureCoord);
+    free(vertexCoord);
+    free(indices);
+
+    renderer->nb_indices = nbIndices;
+
+    return VLC_SUCCESS;
+}
+
+static void DrawWithShaders(struct vlc_gl_renderer *renderer)
+{
+    const struct vlc_gl_interop *interop = renderer->interop;
+    const opengl_vtable_t *vt = renderer->vt;
+    renderer->pf_prepare_shader(renderer, renderer->tex_width,
+                                renderer->tex_height, 1.0f);
+
+    for (unsigned j = 0; j < interop->tex_count; j++) {
+        assert(renderer->textures[j] != 0);
+        vt->ActiveTexture(GL_TEXTURE0+j);
+        vt->BindTexture(interop->tex_target, renderer->textures[j]);
+
+        vt->BindBuffer(GL_ARRAY_BUFFER, renderer->texture_buffer_object[j]);
+
+        assert(renderer->aloc.MultiTexCoord[j] != -1);
+        vt->EnableVertexAttribArray(renderer->aloc.MultiTexCoord[j]);
+        vt->VertexAttribPointer(renderer->aloc.MultiTexCoord[j], 2,
+                                GL_FLOAT, 0, 0, 0);
+    }
+
+    vt->BindBuffer(GL_ARRAY_BUFFER, renderer->vertex_buffer_object);
+    vt->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->index_buffer_object);
+    vt->EnableVertexAttribArray(renderer->aloc.VertexPosition);
+    vt->VertexAttribPointer(renderer->aloc.VertexPosition, 3, GL_FLOAT, 0, 0, 0);
+
+    const GLfloat *tm = NULL;
+    if (interop->ops && interop->ops->get_transform_matrix)
+        tm = interop->ops->get_transform_matrix(interop);
+    if (!tm)
+        tm = identity;
+
+    vt->UniformMatrix4fv(renderer->uloc.TransformMatrix, 1, GL_FALSE, tm);
+
+    vt->UniformMatrix4fv(renderer->uloc.OrientationMatrix, 1, GL_FALSE,
+                         renderer->var.OrientationMatrix);
+    vt->UniformMatrix4fv(renderer->uloc.ProjectionMatrix, 1, GL_FALSE,
+                         renderer->var.ProjectionMatrix);
+    vt->UniformMatrix4fv(renderer->uloc.ViewMatrix, 1, GL_FALSE,
+                         renderer->var.ViewMatrix);
+    vt->UniformMatrix4fv(renderer->uloc.ZoomMatrix, 1, GL_FALSE,
+                         renderer->var.ZoomMatrix);
+
+    vt->DrawElements(GL_TRIANGLES, renderer->nb_indices, GL_UNSIGNED_SHORT, 0);
+}
+
+
+static void GetTextureCropParamsForStereo(unsigned i_nbTextures,
+                                          const float *stereoCoefs,
+                                          const float *stereoOffsets,
+                                          float *left, float *top,
+                                          float *right, float *bottom)
+{
+    for (unsigned i = 0; i < i_nbTextures; ++i)
+    {
+        float f_2eyesWidth = right[i] - left[i];
+        left[i] = left[i] + f_2eyesWidth * stereoOffsets[0];
+        right[i] = left[i] + f_2eyesWidth * stereoCoefs[0];
+
+        float f_2eyesHeight = bottom[i] - top[i];
+        top[i] = top[i] + f_2eyesHeight * stereoOffsets[1];
+        bottom[i] = top[i] + f_2eyesHeight * stereoCoefs[1];
+    }
+}
+
+static void TextureCropForStereo(struct vlc_gl_renderer *renderer,
+                                 float *left, float *top,
+                                 float *right, float *bottom)
+{
+    const struct vlc_gl_interop *interop = renderer->interop;
+
+    float stereoCoefs[2];
+    float stereoOffsets[2];
+
+    switch (renderer->fmt.multiview_mode)
+    {
+    case MULTIVIEW_STEREO_TB:
+        // Display only the left eye.
+        stereoCoefs[0] = 1; stereoCoefs[1] = 0.5;
+        stereoOffsets[0] = 0; stereoOffsets[1] = 0;
+        GetTextureCropParamsForStereo(interop->tex_count,
+                                      stereoCoefs, stereoOffsets,
+                                      left, top, right, bottom);
+        break;
+    case MULTIVIEW_STEREO_SBS:
+        // Display only the left eye.
+        stereoCoefs[0] = 0.5; stereoCoefs[1] = 1;
+        stereoOffsets[0] = 0; stereoOffsets[1] = 0;
+        GetTextureCropParamsForStereo(interop->tex_count,
+                                      stereoCoefs, stereoOffsets,
+                                      left, top, right, bottom);
+        break;
+    default:
+        break;
+    }
+}
+
+int
+vlc_gl_renderer_Draw(struct vlc_gl_renderer *renderer,
+                     const video_format_t *source)
+{
+    const opengl_vtable_t *vt = renderer->vt;
+
+    vt->Clear(GL_COLOR_BUFFER_BIT);
+
+    vt->UseProgram(renderer->program_id);
+
+    if (source->i_x_offset != renderer->last_source.i_x_offset
+     || source->i_y_offset != renderer->last_source.i_y_offset
+     || source->i_visible_width != renderer->last_source.i_visible_width
+     || source->i_visible_height != renderer->last_source.i_visible_height)
+    {
+        float left[PICTURE_PLANE_MAX];
+        float top[PICTURE_PLANE_MAX];
+        float right[PICTURE_PLANE_MAX];
+        float bottom[PICTURE_PLANE_MAX];
+        const struct vlc_gl_interop *interop = renderer->interop;
+        for (unsigned j = 0; j < interop->tex_count; j++)
+        {
+            float scale_w = (float)interop->texs[j].w.num / interop->texs[j].w.den
+                          / renderer->tex_width[j];
+            float scale_h = (float)interop->texs[j].h.num / interop->texs[j].h.den
+                          / renderer->tex_height[j];
+
+            /* Warning: if NPOT is not supported a larger texture is
+               allocated. This will cause right and bottom coordinates to
+               land on the edge of two texels with the texels to the
+               right/bottom uninitialized by the call to
+               glTexSubImage2D. This might cause a green line to appear on
+               the right/bottom of the display.
+               There are two possible solutions:
+               - Manually mirror the edges of the texture.
+               - Add a "-1" when computing right and bottom, however the
+               last row/column might not be displayed at all.
+            */
+            left[j]   = (source->i_x_offset +                       0 ) * scale_w;
+            top[j]    = (source->i_y_offset +                       0 ) * scale_h;
+            right[j]  = (source->i_x_offset + source->i_visible_width ) * scale_w;
+            bottom[j] = (source->i_y_offset + source->i_visible_height) * scale_h;
+        }
+
+        TextureCropForStereo(renderer, left, top, right, bottom);
+        int ret = SetupCoords(renderer, left, top, right, bottom);
+        if (ret != VLC_SUCCESS)
+            return ret;
+
+        renderer->last_source.i_x_offset = source->i_x_offset;
+        renderer->last_source.i_y_offset = source->i_y_offset;
+        renderer->last_source.i_visible_width = source->i_visible_width;
+        renderer->last_source.i_visible_height = source->i_visible_height;
+    }
+    DrawWithShaders(renderer);
+
+    return VLC_SUCCESS;
+}
diff --git a/modules/video_output/opengl/renderer.h b/modules/video_output/opengl/renderer.h
index 80de46c031..a8da814761 100644
--- a/modules/video_output/opengl/renderer.h
+++ b/modules/video_output/opengl/renderer.h
@@ -148,4 +148,57 @@ struct vlc_gl_renderer
                               float alpha);
 };
 
+/**
+ * Create a new renderer
+ *
+ * \param gl the GL context
+ * \param vt the OpenGL functions vtable
+ * \param context the video context
+ * \param fmt the video format
+ * \param supports_npot indicate if the implementation supports non-power-of-2
+ *                      texture size
+ * \param dump_shaders indicate if the shaders must be dumped in logs
+ */
+struct vlc_gl_renderer *
+vlc_gl_renderer_New(vlc_gl_t *gl, const opengl_vtable_t *vt,
+                    vlc_video_context *context, const video_format_t *fmt,
+                    bool supports_npot, bool dump_shaders);
+
+/**
+ * Delete a renderer
+ *
+ * \param renderer the renderer
+ */
+void
+vlc_gl_renderer_Delete(struct vlc_gl_renderer *renderer);
+
+/**
+ * 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_renderer_Prepare(struct vlc_gl_renderer *renderer, picture_t *picture);
+
+/**
+ * Draw the prepared picture
+ *
+ * \param sr the renderer
+ */
+int
+vlc_gl_renderer_Draw(struct vlc_gl_renderer *renderer,
+                     const video_format_t *source);
+
+int
+vlc_gl_renderer_SetViewpoint(struct vlc_gl_renderer *renderer,
+                             const vlc_viewpoint_t *p_vp);
+
+void
+vlc_gl_renderer_SetWindowAspectRatio(struct vlc_gl_renderer *renderer,
+                                     float f_sar);
+
 #endif /* include-guard */
diff --git a/modules/video_output/opengl/vout_helper.c b/modules/video_output/opengl/vout_helper.c
index 98be8f9a78..b7bb6bc363 100644
--- a/modules/video_output/opengl/vout_helper.c
+++ b/modules/video_output/opengl/vout_helper.c
@@ -42,10 +42,9 @@
 #include "gl_util.h"
 #include "vout_helper.h"
 #include "internal.h"
+#include "renderer.h"
 #include "sub_renderer.h"
 
-#define SPHERE_RADIUS 1.f
-
 struct vout_display_opengl_t {
 
     vlc_gl_t   *gl;
@@ -60,431 +59,6 @@ static const vlc_fourcc_t gl_subpicture_chromas[] = {
     0
 };
 
-static const GLfloat identity[] = {
-    1.0f, 0.0f, 0.0f, 0.0f,
-    0.0f, 1.0f, 0.0f, 0.0f,
-    0.0f, 0.0f, 1.0f, 0.0f,
-    0.0f, 0.0f, 0.0f, 1.0f
-};
-
-static void getZoomMatrix(float zoom, GLfloat matrix[static 16]) {
-
-    const GLfloat m[] = {
-        /* x   y     z     w */
-        1.0f, 0.0f, 0.0f, 0.0f,
-        0.0f, 1.0f, 0.0f, 0.0f,
-        0.0f, 0.0f, 1.0f, 0.0f,
-        0.0f, 0.0f, zoom, 1.0f
-    };
-
-    memcpy(matrix, m, sizeof(m));
-}
-
-/* perspective matrix see https://www.opengl.org/sdk/docs/man2/xhtml/gluPerspective.xml */
-static void getProjectionMatrix(float sar, float fovy, GLfloat matrix[static 16]) {
-
-    float zFar  = 1000;
-    float zNear = 0.01;
-
-    float f = 1.f / tanf(fovy / 2.f);
-
-    const GLfloat m[] = {
-        f / sar, 0.f,                   0.f,                0.f,
-        0.f,     f,                     0.f,                0.f,
-        0.f,     0.f,     (zNear + zFar) / (zNear - zFar), -1.f,
-        0.f,     0.f, (2 * zNear * zFar) / (zNear - zFar),  0.f};
-
-     memcpy(matrix, m, sizeof(m));
-}
-
-static void getViewpointMatrixes(struct vlc_gl_renderer *renderer,
-                                 video_projection_mode_t projection_mode)
-{
-    if (projection_mode == PROJECTION_MODE_EQUIRECTANGULAR
-        || projection_mode == PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD)
-    {
-        getProjectionMatrix(renderer->f_sar, renderer->f_fovy,
-                            renderer->var.ProjectionMatrix);
-        getZoomMatrix(renderer->f_z, renderer->var.ZoomMatrix);
-
-        /* renderer->vp has been reversed and is a world transform */
-        vlc_viewpoint_to_4x4(&renderer->vp, renderer->var.ViewMatrix);
-    }
-    else
-    {
-        memcpy(renderer->var.ProjectionMatrix, identity, sizeof(identity));
-        memcpy(renderer->var.ZoomMatrix, identity, sizeof(identity));
-        memcpy(renderer->var.ViewMatrix, identity, sizeof(identity));
-    }
-
-}
-
-static void getOrientationTransformMatrix(video_orientation_t orientation,
-                                          GLfloat matrix[static 16])
-{
-    memcpy(matrix, identity, sizeof(identity));
-
-    const int k_cos_pi = -1;
-    const int k_cos_pi_2 = 0;
-    const int k_cos_n_pi_2 = 0;
-
-    const int k_sin_pi = 0;
-    const int k_sin_pi_2 = 1;
-    const int k_sin_n_pi_2 = -1;
-
-    switch (orientation) {
-
-        case ORIENT_ROTATED_90:
-            matrix[0 * 4 + 0] = k_cos_pi_2;
-            matrix[0 * 4 + 1] = -k_sin_pi_2;
-            matrix[1 * 4 + 0] = k_sin_pi_2;
-            matrix[1 * 4 + 1] = k_cos_pi_2;
-            matrix[3 * 4 + 1] = 1;
-            break;
-        case ORIENT_ROTATED_180:
-            matrix[0 * 4 + 0] = k_cos_pi;
-            matrix[0 * 4 + 1] = -k_sin_pi;
-            matrix[1 * 4 + 0] = k_sin_pi;
-            matrix[1 * 4 + 1] = k_cos_pi;
-            matrix[3 * 4 + 0] = 1;
-            matrix[3 * 4 + 1] = 1;
-            break;
-        case ORIENT_ROTATED_270:
-            matrix[0 * 4 + 0] = k_cos_n_pi_2;
-            matrix[0 * 4 + 1] = -k_sin_n_pi_2;
-            matrix[1 * 4 + 0] = k_sin_n_pi_2;
-            matrix[1 * 4 + 1] = k_cos_n_pi_2;
-            matrix[3 * 4 + 0] = 1;
-            break;
-        case ORIENT_HFLIPPED:
-            matrix[0 * 4 + 0] = -1;
-            matrix[3 * 4 + 0] = 1;
-            break;
-        case ORIENT_VFLIPPED:
-            matrix[1 * 4 + 1] = -1;
-            matrix[3 * 4 + 1] = 1;
-            break;
-        case ORIENT_TRANSPOSED:
-            matrix[0 * 4 + 0] = 0;
-            matrix[1 * 4 + 1] = 0;
-            matrix[2 * 4 + 2] = -1;
-            matrix[0 * 4 + 1] = 1;
-            matrix[1 * 4 + 0] = 1;
-            break;
-        case ORIENT_ANTI_TRANSPOSED:
-            matrix[0 * 4 + 0] = 0;
-            matrix[1 * 4 + 1] = 0;
-            matrix[2 * 4 + 2] = -1;
-            matrix[0 * 4 + 1] = -1;
-            matrix[1 * 4 + 0] = -1;
-            matrix[3 * 4 + 0] = 1;
-            matrix[3 * 4 + 1] = 1;
-            break;
-        default:
-            break;
-    }
-}
-
-static GLuint BuildVertexShader(const struct vlc_gl_renderer *renderer,
-                                unsigned plane_count)
-{
-    const opengl_vtable_t *vt = renderer->vt;
-
-    /* Basic vertex shader */
-    static const char *template =
-        "#version %u\n"
-        "varying vec2 TexCoord0;\n"
-        "attribute vec4 MultiTexCoord0;\n"
-        "%s%s"
-        "attribute vec3 VertexPosition;\n"
-        "uniform mat4 TransformMatrix;\n"
-        "uniform mat4 OrientationMatrix;\n"
-        "uniform mat4 ProjectionMatrix;\n"
-        "uniform mat4 ZoomMatrix;\n"
-        "uniform mat4 ViewMatrix;\n"
-        "void main() {\n"
-        " TexCoord0 = vec4(TransformMatrix * OrientationMatrix * MultiTexCoord0).st;\n"
-        "%s%s"
-        " gl_Position = ProjectionMatrix * ZoomMatrix * ViewMatrix\n"
-        "               * vec4(VertexPosition, 1.0);\n"
-        "}";
-
-    const char *coord1_header = plane_count > 1 ?
-        "varying vec2 TexCoord1;\nattribute vec4 MultiTexCoord1;\n" : "";
-    const char *coord1_code = plane_count > 1 ?
-        " TexCoord1 = vec4(TransformMatrix * OrientationMatrix * MultiTexCoord1).st;\n" : "";
-    const char *coord2_header = plane_count > 2 ?
-        "varying vec2 TexCoord2;\nattribute vec4 MultiTexCoord2;\n" : "";
-    const char *coord2_code = plane_count > 2 ?
-        " TexCoord2 = vec4(TransformMatrix * OrientationMatrix * MultiTexCoord2).st;\n" : "";
-
-    char *code;
-    if (asprintf(&code, template, renderer->glsl_version, coord1_header,
-                 coord2_header, coord1_code, coord2_code) < 0)
-        return 0;
-
-    GLuint shader = vt->CreateShader(GL_VERTEX_SHADER);
-    vt->ShaderSource(shader, 1, (const char **) &code, NULL);
-    if (renderer->b_dump_shaders)
-        msg_Dbg(renderer->gl, "\n=== Vertex shader for fourcc: %4.4s ===\n%s\n",
-                (const char *) &renderer->interop->fmt.i_chroma, code);
-    vt->CompileShader(shader);
-    free(code);
-    return shader;
-}
-
-static int
-opengl_link_program(struct vlc_gl_renderer *renderer)
-{
-    struct vlc_gl_interop *interop = renderer->interop;
-    const opengl_vtable_t *vt = renderer->vt;
-
-    GLuint vertex_shader = BuildVertexShader(renderer, interop->tex_count);
-    if (!vertex_shader)
-        return VLC_EGENERIC;
-
-    GLuint fragment_shader =
-        opengl_fragment_shader_init(renderer, interop->tex_target,
-                                    interop->sw_fmt.i_chroma,
-                                    interop->sw_fmt.space);
-    if (!fragment_shader)
-        return VLC_EGENERIC;
-
-    assert(interop->tex_target != 0 &&
-           interop->tex_count > 0 &&
-           interop->ops->update_textures != NULL &&
-           renderer->pf_fetch_locations != NULL &&
-           renderer->pf_prepare_shader != NULL);
-
-    GLuint shaders[] = { fragment_shader, vertex_shader };
-
-    /* Check shaders messages */
-    for (unsigned i = 0; i < 2; i++) {
-        int infoLength;
-        vt->GetShaderiv(shaders[i], GL_INFO_LOG_LENGTH, &infoLength);
-        if (infoLength <= 1)
-            continue;
-
-        char *infolog = malloc(infoLength);
-        if (infolog != NULL)
-        {
-            int charsWritten;
-            vt->GetShaderInfoLog(shaders[i], infoLength, &charsWritten,
-                                 infolog);
-            msg_Err(renderer->gl, "shader %u: %s", i, infolog);
-            free(infolog);
-        }
-    }
-
-    GLuint program_id = renderer->program_id = vt->CreateProgram();
-    vt->AttachShader(program_id, fragment_shader);
-    vt->AttachShader(program_id, vertex_shader);
-    vt->LinkProgram(program_id);
-
-    vt->DeleteShader(vertex_shader);
-    vt->DeleteShader(fragment_shader);
-
-    /* Check program messages */
-    int infoLength = 0;
-    vt->GetProgramiv(program_id, GL_INFO_LOG_LENGTH, &infoLength);
-    if (infoLength > 1)
-    {
-        char *infolog = malloc(infoLength);
-        if (infolog != NULL)
-        {
-            int charsWritten;
-            vt->GetProgramInfoLog(program_id, infoLength, &charsWritten,
-                                  infolog);
-            msg_Err(renderer->gl, "shader program: %s", infolog);
-            free(infolog);
-        }
-
-        /* If there is some message, better to check linking is ok */
-        GLint link_status = GL_TRUE;
-        vt->GetProgramiv(program_id, GL_LINK_STATUS, &link_status);
-        if (link_status == GL_FALSE)
-        {
-            msg_Err(renderer->gl, "Unable to use program");
-            goto error;
-        }
-    }
-
-    /* Fetch UniformLocations and AttribLocations */
-#define GET_LOC(type, x, str) do { \
-    x = vt->Get##type##Location(program_id, str); \
-    assert(x != -1); \
-    if (x == -1) { \
-        msg_Err(renderer->gl, "Unable to Get"#type"Location(%s)", str); \
-        goto error; \
-    } \
-} while (0)
-#define GET_ULOC(x, str) GET_LOC(Uniform, renderer->uloc.x, str)
-#define GET_ALOC(x, str) GET_LOC(Attrib, renderer->aloc.x, str)
-    GET_ULOC(TransformMatrix, "TransformMatrix");
-    GET_ULOC(OrientationMatrix, "OrientationMatrix");
-    GET_ULOC(ProjectionMatrix, "ProjectionMatrix");
-    GET_ULOC(ViewMatrix, "ViewMatrix");
-    GET_ULOC(ZoomMatrix, "ZoomMatrix");
-
-    GET_ALOC(VertexPosition, "VertexPosition");
-    GET_ALOC(MultiTexCoord[0], "MultiTexCoord0");
-    /* MultiTexCoord 1 and 2 can be optimized out if not used */
-    if (interop->tex_count > 1)
-        GET_ALOC(MultiTexCoord[1], "MultiTexCoord1");
-    else
-        renderer->aloc.MultiTexCoord[1] = -1;
-    if (interop->tex_count > 2)
-        GET_ALOC(MultiTexCoord[2], "MultiTexCoord2");
-    else
-        renderer->aloc.MultiTexCoord[2] = -1;
-#undef GET_LOC
-#undef GET_ULOC
-#undef GET_ALOC
-    int ret = renderer->pf_fetch_locations(renderer, program_id);
-    assert(ret == VLC_SUCCESS);
-    if (ret != VLC_SUCCESS)
-    {
-        msg_Err(renderer->gl, "Unable to get locations from tex_conv");
-        goto error;
-    }
-
-    return VLC_SUCCESS;
-
-error:
-    vt->DeleteProgram(program_id);
-    renderer->program_id = 0;
-    return VLC_EGENERIC;
-}
-
-static void
-DeleteRenderer(struct vlc_gl_renderer *renderer)
-{
-    const opengl_vtable_t *vt = renderer->vt;
-    struct vlc_gl_interop *interop = renderer->interop;
-
-    vt->DeleteBuffers(1, &renderer->vertex_buffer_object);
-    vt->DeleteBuffers(1, &renderer->index_buffer_object);
-    vt->DeleteBuffers(interop->tex_count, renderer->texture_buffer_object);
-
-    if (!interop->handle_texs_gen)
-        vt->DeleteTextures(interop->tex_count, renderer->textures);
-
-    vlc_gl_interop_Delete(interop);
-
-    if (renderer->program_id != 0)
-        renderer->vt->DeleteProgram(renderer->program_id);
-
-#ifdef HAVE_LIBPLACEBO
-    FREENULL(renderer->uloc.pl_vars);
-    if (renderer->pl_ctx)
-        pl_context_destroy(&renderer->pl_ctx);
-#endif
-
-    free(renderer);
-}
-
-static struct vlc_gl_renderer *
-CreateRenderer(vlc_gl_t *gl, const opengl_vtable_t *vt,
-               vlc_video_context *context, const video_format_t *fmt,
-               bool supports_npot, bool b_dump_shaders)
-{
-    struct vlc_gl_renderer *renderer = calloc(1, sizeof(*renderer));
-    if (!renderer)
-        return NULL;
-
-    struct vlc_gl_interop *interop =
-        vlc_gl_interop_New(gl, vt, context, fmt, false);
-    if (!interop)
-    {
-        free(renderer);
-        return NULL;
-    }
-
-    renderer->interop = interop;
-
-    renderer->gl = gl;
-    renderer->vt = vt;
-    renderer->b_dump_shaders = b_dump_shaders;
-#if defined(USE_OPENGL_ES2)
-    renderer->glsl_version = 100;
-    renderer->glsl_precision_header = "precision highp float;\n";
-#else
-    renderer->glsl_version = 120;
-    renderer->glsl_precision_header = "";
-#endif
-
-#ifdef HAVE_LIBPLACEBO
-    // Create the main libplacebo context
-    renderer->pl_ctx = vlc_placebo_Create(VLC_OBJECT(gl));
-    if (renderer->pl_ctx) {
-#   if PL_API_VER >= 20
-        renderer->pl_sh = pl_shader_alloc(renderer->pl_ctx, NULL);
-#   elif PL_API_VER >= 6
-        renderer->pl_sh = pl_shader_alloc(renderer->pl_ctx, NULL, 0);
-#   else
-        renderer->pl_sh = pl_shader_alloc(renderer->pl_ctx, NULL, 0, 0);
-#   endif
-    }
-#endif
-
-    int ret = opengl_link_program(renderer);
-    if (ret != VLC_SUCCESS)
-    {
-        DeleteRenderer(renderer);
-        return NULL;
-    }
-
-    getOrientationTransformMatrix(interop->fmt.orientation,
-                                  renderer->var.OrientationMatrix);
-    getViewpointMatrixes(renderer, interop->fmt.projection_mode);
-
-    /* Update the fmt to main program one */
-    renderer->fmt = interop->fmt;
-    /* The orientation is handled by the orientation matrix */
-    renderer->fmt.orientation = fmt->orientation;
-
-    /* Texture size */
-    for (unsigned j = 0; j < interop->tex_count; j++) {
-        const GLsizei w = renderer->fmt.i_visible_width  * interop->texs[j].w.num
-                        / interop->texs[j].w.den;
-        const GLsizei h = renderer->fmt.i_visible_height * interop->texs[j].h.num
-                        / interop->texs[j].h.den;
-        if (supports_npot) {
-            renderer->tex_width[j]  = w;
-            renderer->tex_height[j] = h;
-        } else {
-            renderer->tex_width[j]  = vlc_align_pot(w);
-            renderer->tex_height[j] = vlc_align_pot(h);
-        }
-    }
-
-    if (!interop->handle_texs_gen)
-    {
-        ret = vlc_gl_interop_GenerateTextures(interop, renderer->tex_width,
-                                              renderer->tex_height,
-                                              renderer->textures);
-        if (ret != VLC_SUCCESS)
-        {
-            DeleteRenderer(renderer);
-            return NULL;
-        }
-    }
-
-    /* */
-    vt->Disable(GL_BLEND);
-    vt->Disable(GL_DEPTH_TEST);
-    vt->DepthMask(GL_FALSE);
-    vt->Enable(GL_CULL_FACE);
-    vt->ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-    vt->Clear(GL_COLOR_BUFFER_BIT);
-
-    vt->GenBuffers(1, &renderer->vertex_buffer_object);
-    vt->GenBuffers(1, &renderer->index_buffer_object);
-    vt->GenBuffers(interop->tex_count, renderer->texture_buffer_object);
-
-    return renderer;
-}
-
 static void
 ResizeFormatToGLMaxTexSize(video_format_t *fmt, unsigned int max_tex_size)
 {
@@ -677,8 +251,8 @@ vout_display_opengl_t *vout_display_opengl_New(video_format_t *fmt,
 
     GL_ASSERT_NOERROR();
     struct vlc_gl_renderer *renderer = vgl->renderer =
-        CreateRenderer(gl, &vgl->vt, context, fmt, supports_npot,
-                       b_dump_shaders);
+        vlc_gl_renderer_New(gl, &vgl->vt, context, fmt, supports_npot,
+                            b_dump_shaders);
     if (!vgl->renderer)
     {
         msg_Warn(gl, "Could not create renderer for %4.4s",
@@ -715,80 +289,23 @@ void vout_display_opengl_Delete(vout_display_opengl_t *vgl)
     vgl->vt.Flush();
 
     vlc_gl_sub_renderer_Delete(vgl->sub_renderer);
-    DeleteRenderer(vgl->renderer);
+    vlc_gl_renderer_Delete(vgl->renderer);
 
     GL_ASSERT_NOERROR();
 
     free(vgl);
 }
 
-static void UpdateZ(struct vlc_gl_renderer *renderer)
-{
-    /* Do trigonometry to calculate the minimal z value
-     * that will allow us to zoom out without seeing the outside of the
-     * sphere (black borders). */
-    float tan_fovx_2 = tanf(renderer->f_fovx / 2);
-    float tan_fovy_2 = tanf(renderer->f_fovy / 2);
-    float z_min = - SPHERE_RADIUS / sinf(atanf(sqrtf(
-                    tan_fovx_2 * tan_fovx_2 + tan_fovy_2 * tan_fovy_2)));
-
-    /* The FOV value above which z is dynamically calculated. */
-    const float z_thresh = 90.f;
-
-    if (renderer->f_fovx <= z_thresh * M_PI / 180)
-        renderer->f_z = 0;
-    else
-    {
-        float f = z_min / ((FIELD_OF_VIEW_DEGREES_MAX - z_thresh) * M_PI / 180);
-        renderer->f_z = f * renderer->f_fovx - f * z_thresh * M_PI / 180;
-        if (renderer->f_z < z_min)
-            renderer->f_z = z_min;
-    }
-}
-
-static void UpdateFOVy(struct vlc_gl_renderer *renderer)
-{
-    renderer->f_fovy = 2 * atanf(tanf(renderer->f_fovx / 2) / renderer->f_sar);
-}
-
 int vout_display_opengl_SetViewpoint(vout_display_opengl_t *vgl,
                                      const vlc_viewpoint_t *p_vp)
 {
-    struct vlc_gl_renderer *renderer = vgl->renderer;
-    if (p_vp->fov > FIELD_OF_VIEW_DEGREES_MAX
-            || p_vp->fov < FIELD_OF_VIEW_DEGREES_MIN)
-        return VLC_EBADVAR;
-
-    // Convert degree into radian
-    float f_fovx = p_vp->fov * (float)M_PI / 180.f;
-
-    /* vgl->vp needs to be converted into world transform */
-    vlc_viewpoint_reverse(&renderer->vp, p_vp);
-
-    if (fabsf(f_fovx - renderer->f_fovx) >= 0.001f)
-    {
-        /* FOVx has changed. */
-        renderer->f_fovx = f_fovx;
-        UpdateFOVy(renderer);
-        UpdateZ(renderer);
-    }
-    getViewpointMatrixes(renderer, renderer->fmt.projection_mode);
-
-    return VLC_SUCCESS;
+    return vlc_gl_renderer_SetViewpoint(vgl->renderer, p_vp);
 }
 
-
 void vout_display_opengl_SetWindowAspectRatio(vout_display_opengl_t *vgl,
                                               float f_sar)
 {
-    struct vlc_gl_renderer *renderer = vgl->renderer;
-    /* Each time the window size changes, we must recompute the minimum zoom
-     * since the aspect ration changes.
-     * We must also set the new current zoom value. */
-    renderer->f_sar = f_sar;
-    UpdateFOVy(renderer);
-    UpdateZ(renderer);
-    getViewpointMatrixes(renderer, renderer->fmt.projection_mode);
+    vlc_gl_renderer_SetWindowAspectRatio(vgl->renderer, f_sar);
 }
 
 void vout_display_opengl_Viewport(vout_display_opengl_t *vgl, int x, int y,
@@ -802,14 +319,7 @@ int vout_display_opengl_Prepare(vout_display_opengl_t *vgl,
 {
     GL_ASSERT_NOERROR();
 
-    struct vlc_gl_renderer *renderer = vgl->renderer;
-    const struct vlc_gl_interop *interop = renderer->interop;
-
-    /* Update the texture */
-    int ret = interop->ops->update_textures(interop, renderer->textures,
-                                            renderer->tex_width,
-                                            renderer->tex_height, picture,
-                                            NULL);
+    int ret = vlc_gl_renderer_Prepare(vgl->renderer, picture);
     if (ret != VLC_SUCCESS)
         return ret;
 
@@ -817,497 +327,20 @@ int vout_display_opengl_Prepare(vout_display_opengl_t *vgl,
     GL_ASSERT_NOERROR();
     return ret;
 }
-
-static int BuildSphere(unsigned nbPlanes,
-                        GLfloat **vertexCoord, GLfloat **textureCoord, unsigned *nbVertices,
-                        GLushort **indices, unsigned *nbIndices,
-                        const float *left, const float *top,
-                        const float *right, const float *bottom)
-{
-    unsigned nbLatBands = 128;
-    unsigned nbLonBands = 128;
-
-    *nbVertices = (nbLatBands + 1) * (nbLonBands + 1);
-    *nbIndices = nbLatBands * nbLonBands * 3 * 2;
-
-    *vertexCoord = vlc_alloc(*nbVertices * 3, sizeof(GLfloat));
-    if (*vertexCoord == NULL)
-        return VLC_ENOMEM;
-    *textureCoord = vlc_alloc(nbPlanes * *nbVertices * 2, sizeof(GLfloat));
-    if (*textureCoord == NULL)
-    {
-        free(*vertexCoord);
-        return VLC_ENOMEM;
-    }
-    *indices = vlc_alloc(*nbIndices, sizeof(GLushort));
-    if (*indices == NULL)
-    {
-        free(*textureCoord);
-        free(*vertexCoord);
-        return VLC_ENOMEM;
-    }
-
-    for (unsigned lat = 0; lat <= nbLatBands; lat++) {
-        float theta = lat * (float) M_PI / nbLatBands;
-        float sinTheta, cosTheta;
-
-        sincosf(theta, &sinTheta, &cosTheta);
-
-        for (unsigned lon = 0; lon <= nbLonBands; lon++) {
-            float phi = lon * 2 * (float) M_PI / nbLonBands;
-            float sinPhi, cosPhi;
-
-            sincosf(phi, &sinPhi, &cosPhi);
-
-            float x = cosPhi * sinTheta;
-            float y = cosTheta;
-            float z = sinPhi * sinTheta;
-
-            unsigned off1 = (lat * (nbLonBands + 1) + lon) * 3;
-            (*vertexCoord)[off1] = SPHERE_RADIUS * x;
-            (*vertexCoord)[off1 + 1] = SPHERE_RADIUS * y;
-            (*vertexCoord)[off1 + 2] = SPHERE_RADIUS * z;
-
-            for (unsigned p = 0; p < nbPlanes; ++p)
-            {
-                unsigned off2 = (p * (nbLatBands + 1) * (nbLonBands + 1)
-                                + lat * (nbLonBands + 1) + lon) * 2;
-                float width = right[p] - left[p];
-                float height = bottom[p] - top[p];
-                float u = (float)lon / nbLonBands * width;
-                float v = (float)lat / nbLatBands * height;
-                (*textureCoord)[off2] = u;
-                (*textureCoord)[off2 + 1] = v;
-            }
-        }
-    }
-
-    for (unsigned lat = 0; lat < nbLatBands; lat++) {
-        for (unsigned lon = 0; lon < nbLonBands; lon++) {
-            unsigned first = (lat * (nbLonBands + 1)) + lon;
-            unsigned second = first + nbLonBands + 1;
-
-            unsigned off = (lat * nbLatBands + lon) * 3 * 2;
-
-            (*indices)[off] = first;
-            (*indices)[off + 1] = second;
-            (*indices)[off + 2] = first + 1;
-
-            (*indices)[off + 3] = second;
-            (*indices)[off + 4] = second + 1;
-            (*indices)[off + 5] = first + 1;
-        }
-    }
-
-    return VLC_SUCCESS;
-}
-
-
-static int BuildCube(unsigned nbPlanes,
-                     float padW, float padH,
-                     GLfloat **vertexCoord, GLfloat **textureCoord, unsigned *nbVertices,
-                     GLushort **indices, unsigned *nbIndices,
-                     const float *left, const float *top,
-                     const float *right, const float *bottom)
-{
-    *nbVertices = 4 * 6;
-    *nbIndices = 6 * 6;
-
-    *vertexCoord = vlc_alloc(*nbVertices * 3, sizeof(GLfloat));
-    if (*vertexCoord == NULL)
-        return VLC_ENOMEM;
-    *textureCoord = vlc_alloc(nbPlanes * *nbVertices * 2, sizeof(GLfloat));
-    if (*textureCoord == NULL)
-    {
-        free(*vertexCoord);
-        return VLC_ENOMEM;
-    }
-    *indices = vlc_alloc(*nbIndices, sizeof(GLushort));
-    if (*indices == NULL)
-    {
-        free(*textureCoord);
-        free(*vertexCoord);
-        return VLC_ENOMEM;
-    }
-
-    static const GLfloat coord[] = {
-        -1.0,    1.0,    -1.0f, // front
-        -1.0,    -1.0,   -1.0f,
-        1.0,     1.0,    -1.0f,
-        1.0,     -1.0,   -1.0f,
-
-        -1.0,    1.0,    1.0f, // back
-        -1.0,    -1.0,   1.0f,
-        1.0,     1.0,    1.0f,
-        1.0,     -1.0,   1.0f,
-
-        -1.0,    1.0,    -1.0f, // left
-        -1.0,    -1.0,   -1.0f,
-        -1.0,     1.0,    1.0f,
-        -1.0,     -1.0,   1.0f,
-
-        1.0f,    1.0,    -1.0f, // right
-        1.0f,   -1.0,    -1.0f,
-        1.0f,   1.0,     1.0f,
-        1.0f,   -1.0,    1.0f,
-
-        -1.0,    -1.0,    1.0f, // bottom
-        -1.0,    -1.0,   -1.0f,
-        1.0,     -1.0,    1.0f,
-        1.0,     -1.0,   -1.0f,
-
-        -1.0,    1.0,    1.0f, // top
-        -1.0,    1.0,   -1.0f,
-        1.0,     1.0,    1.0f,
-        1.0,     1.0,   -1.0f,
-    };
-
-    memcpy(*vertexCoord, coord, *nbVertices * 3 * sizeof(GLfloat));
-
-    for (unsigned p = 0; p < nbPlanes; ++p)
-    {
-        float width = right[p] - left[p];
-        float height = bottom[p] - top[p];
-
-        float col[] = {left[p],
-                       left[p] + width * 1.f/3,
-                       left[p] + width * 2.f/3,
-                       left[p] + width};
-
-        float row[] = {top[p],
-                       top[p] + height * 1.f/2,
-                       top[p] + height};
-
-        const GLfloat tex[] = {
-            col[1] + padW, row[1] + padH, // front
-            col[1] + padW, row[2] - padH,
-            col[2] - padW, row[1] + padH,
-            col[2] - padW, row[2] - padH,
-
-            col[3] - padW, row[1] + padH, // back
-            col[3] - padW, row[2] - padH,
-            col[2] + padW, row[1] + padH,
-            col[2] + padW, row[2] - padH,
-
-            col[2] - padW, row[0] + padH, // left
-            col[2] - padW, row[1] - padH,
-            col[1] + padW, row[0] + padH,
-            col[1] + padW, row[1] - padH,
-
-            col[0] + padW, row[0] + padH, // right
-            col[0] + padW, row[1] - padH,
-            col[1] - padW, row[0] + padH,
-            col[1] - padW, row[1] - padH,
-
-            col[0] + padW, row[2] - padH, // bottom
-            col[0] + padW, row[1] + padH,
-            col[1] - padW, row[2] - padH,
-            col[1] - padW, row[1] + padH,
-
-            col[2] + padW, row[0] + padH, // top
-            col[2] + padW, row[1] - padH,
-            col[3] - padW, row[0] + padH,
-            col[3] - padW, row[1] - padH,
-        };
-
-        memcpy(*textureCoord + p * *nbVertices * 2, tex,
-               *nbVertices * 2 * sizeof(GLfloat));
-    }
-
-    const GLushort ind[] = {
-        0, 1, 2,       2, 1, 3, // front
-        6, 7, 4,       4, 7, 5, // back
-        10, 11, 8,     8, 11, 9, // left
-        12, 13, 14,    14, 13, 15, // right
-        18, 19, 16,    16, 19, 17, // bottom
-        20, 21, 22,    22, 21, 23, // top
-    };
-
-    memcpy(*indices, ind, *nbIndices * sizeof(GLushort));
-
-    return VLC_SUCCESS;
-}
-
-static int BuildRectangle(unsigned nbPlanes,
-                          GLfloat **vertexCoord, GLfloat **textureCoord, unsigned *nbVertices,
-                          GLushort **indices, unsigned *nbIndices,
-                          const float *left, const float *top,
-                          const float *right, const float *bottom)
-{
-    *nbVertices = 4;
-    *nbIndices = 6;
-
-    *vertexCoord = vlc_alloc(*nbVertices * 3, sizeof(GLfloat));
-    if (*vertexCoord == NULL)
-        return VLC_ENOMEM;
-    *textureCoord = vlc_alloc(nbPlanes * *nbVertices * 2, sizeof(GLfloat));
-    if (*textureCoord == NULL)
-    {
-        free(*vertexCoord);
-        return VLC_ENOMEM;
-    }
-    *indices = vlc_alloc(*nbIndices, sizeof(GLushort));
-    if (*indices == NULL)
-    {
-        free(*textureCoord);
-        free(*vertexCoord);
-        return VLC_ENOMEM;
-    }
-
-    static const GLfloat coord[] = {
-       -1.0,    1.0,    -1.0f,
-       -1.0,    -1.0,   -1.0f,
-       1.0,     1.0,    -1.0f,
-       1.0,     -1.0,   -1.0f
-    };
-
-    memcpy(*vertexCoord, coord, *nbVertices * 3 * sizeof(GLfloat));
-
-    for (unsigned p = 0; p < nbPlanes; ++p)
-    {
-        const GLfloat tex[] = {
-            left[p],  top[p],
-            left[p],  bottom[p],
-            right[p], top[p],
-            right[p], bottom[p]
-        };
-
-        memcpy(*textureCoord + p * *nbVertices * 2, tex,
-               *nbVertices * 2 * sizeof(GLfloat));
-    }
-
-    const GLushort ind[] = {
-        0, 1, 2,
-        2, 1, 3
-    };
-
-    memcpy(*indices, ind, *nbIndices * sizeof(GLushort));
-
-    return VLC_SUCCESS;
-}
-
-static int SetupCoords(struct vlc_gl_renderer *renderer,
-                       const float *left, const float *top,
-                       const float *right, const float *bottom)
-{
-    const struct vlc_gl_interop *interop = renderer->interop;
-    const opengl_vtable_t *vt = renderer->vt;
-
-    GLfloat *vertexCoord, *textureCoord;
-    GLushort *indices;
-    unsigned nbVertices, nbIndices;
-
-    int i_ret;
-    switch (renderer->fmt.projection_mode)
-    {
-    case PROJECTION_MODE_RECTANGULAR:
-        i_ret = BuildRectangle(interop->tex_count,
-                               &vertexCoord, &textureCoord, &nbVertices,
-                               &indices, &nbIndices,
-                               left, top, right, bottom);
-        break;
-    case PROJECTION_MODE_EQUIRECTANGULAR:
-        i_ret = BuildSphere(interop->tex_count,
-                            &vertexCoord, &textureCoord, &nbVertices,
-                            &indices, &nbIndices,
-                            left, top, right, bottom);
-        break;
-    case PROJECTION_MODE_CUBEMAP_LAYOUT_STANDARD:
-        i_ret = BuildCube(interop->tex_count,
-                          (float)renderer->fmt.i_cubemap_padding / renderer->fmt.i_width,
-                          (float)renderer->fmt.i_cubemap_padding / renderer->fmt.i_height,
-                          &vertexCoord, &textureCoord, &nbVertices,
-                          &indices, &nbIndices,
-                          left, top, right, bottom);
-        break;
-    default:
-        i_ret = VLC_EGENERIC;
-        break;
-    }
-
-    if (i_ret != VLC_SUCCESS)
-        return i_ret;
-
-    for (unsigned j = 0; j < interop->tex_count; j++)
-    {
-        vt->BindBuffer(GL_ARRAY_BUFFER, renderer->texture_buffer_object[j]);
-        vt->BufferData(GL_ARRAY_BUFFER, nbVertices * 2 * sizeof(GLfloat),
-                       textureCoord + j * nbVertices * 2, GL_STATIC_DRAW);
-    }
-
-    vt->BindBuffer(GL_ARRAY_BUFFER, renderer->vertex_buffer_object);
-    vt->BufferData(GL_ARRAY_BUFFER, nbVertices * 3 * sizeof(GLfloat),
-                   vertexCoord, GL_STATIC_DRAW);
-
-    vt->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->index_buffer_object);
-    vt->BufferData(GL_ELEMENT_ARRAY_BUFFER, nbIndices * sizeof(GLushort),
-                   indices, GL_STATIC_DRAW);
-
-    free(textureCoord);
-    free(vertexCoord);
-    free(indices);
-
-    renderer->nb_indices = nbIndices;
-
-    return VLC_SUCCESS;
-}
-
-static void DrawWithShaders(struct vlc_gl_renderer *renderer)
-{
-    const struct vlc_gl_interop *interop = renderer->interop;
-    const opengl_vtable_t *vt = renderer->vt;
-    renderer->pf_prepare_shader(renderer, renderer->tex_width,
-                                renderer->tex_height, 1.0f);
-
-    for (unsigned j = 0; j < interop->tex_count; j++) {
-        assert(renderer->textures[j] != 0);
-        vt->ActiveTexture(GL_TEXTURE0+j);
-        vt->BindTexture(interop->tex_target, renderer->textures[j]);
-
-        vt->BindBuffer(GL_ARRAY_BUFFER, renderer->texture_buffer_object[j]);
-
-        assert(renderer->aloc.MultiTexCoord[j] != -1);
-        vt->EnableVertexAttribArray(renderer->aloc.MultiTexCoord[j]);
-        vt->VertexAttribPointer(renderer->aloc.MultiTexCoord[j], 2,
-                                GL_FLOAT, 0, 0, 0);
-    }
-
-    vt->BindBuffer(GL_ARRAY_BUFFER, renderer->vertex_buffer_object);
-    vt->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->index_buffer_object);
-    vt->EnableVertexAttribArray(renderer->aloc.VertexPosition);
-    vt->VertexAttribPointer(renderer->aloc.VertexPosition, 3, GL_FLOAT, 0, 0, 0);
-
-    const GLfloat *tm = NULL;
-    if (interop->ops && interop->ops->get_transform_matrix)
-        tm = interop->ops->get_transform_matrix(interop);
-    if (!tm)
-        tm = identity;
-
-    vt->UniformMatrix4fv(renderer->uloc.TransformMatrix, 1, GL_FALSE, tm);
-
-    vt->UniformMatrix4fv(renderer->uloc.OrientationMatrix, 1, GL_FALSE,
-                         renderer->var.OrientationMatrix);
-    vt->UniformMatrix4fv(renderer->uloc.ProjectionMatrix, 1, GL_FALSE,
-                         renderer->var.ProjectionMatrix);
-    vt->UniformMatrix4fv(renderer->uloc.ViewMatrix, 1, GL_FALSE,
-                         renderer->var.ViewMatrix);
-    vt->UniformMatrix4fv(renderer->uloc.ZoomMatrix, 1, GL_FALSE,
-                         renderer->var.ZoomMatrix);
-
-    vt->DrawElements(GL_TRIANGLES, renderer->nb_indices, GL_UNSIGNED_SHORT, 0);
-}
-
-
-static void GetTextureCropParamsForStereo(unsigned i_nbTextures,
-                                          const float *stereoCoefs,
-                                          const float *stereoOffsets,
-                                          float *left, float *top,
-                                          float *right, float *bottom)
-{
-    for (unsigned i = 0; i < i_nbTextures; ++i)
-    {
-        float f_2eyesWidth = right[i] - left[i];
-        left[i] = left[i] + f_2eyesWidth * stereoOffsets[0];
-        right[i] = left[i] + f_2eyesWidth * stereoCoefs[0];
-
-        float f_2eyesHeight = bottom[i] - top[i];
-        top[i] = top[i] + f_2eyesHeight * stereoOffsets[1];
-        bottom[i] = top[i] + f_2eyesHeight * stereoCoefs[1];
-    }
-}
-
-static void TextureCropForStereo(struct vlc_gl_renderer *renderer,
-                                 float *left, float *top,
-                                 float *right, float *bottom)
-{
-    const struct vlc_gl_interop *interop = renderer->interop;
-
-    float stereoCoefs[2];
-    float stereoOffsets[2];
-
-    switch (renderer->fmt.multiview_mode)
-    {
-    case MULTIVIEW_STEREO_TB:
-        // Display only the left eye.
-        stereoCoefs[0] = 1; stereoCoefs[1] = 0.5;
-        stereoOffsets[0] = 0; stereoOffsets[1] = 0;
-        GetTextureCropParamsForStereo(interop->tex_count,
-                                      stereoCoefs, stereoOffsets,
-                                      left, top, right, bottom);
-        break;
-    case MULTIVIEW_STEREO_SBS:
-        // Display only the left eye.
-        stereoCoefs[0] = 0.5; stereoCoefs[1] = 1;
-        stereoOffsets[0] = 0; stereoOffsets[1] = 0;
-        GetTextureCropParamsForStereo(interop->tex_count,
-                                      stereoCoefs, stereoOffsets,
-                                      left, top, right, bottom);
-        break;
-    default:
-        break;
-    }
-}
-
 int vout_display_opengl_Display(vout_display_opengl_t *vgl,
                                 const video_format_t *source)
 {
     GL_ASSERT_NOERROR();
-    struct vlc_gl_renderer *renderer = vgl->renderer;
 
     /* Why drawing here and not in Render()? Because this way, the
        OpenGL providers can call vout_display_opengl_Display to force redraw.
        Currently, the OS X provider uses it to get a smooth window resizing */
-    vgl->vt.Clear(GL_COLOR_BUFFER_BIT);
-
-    vgl->vt.UseProgram(vgl->renderer->program_id);
 
-    if (source->i_x_offset != renderer->last_source.i_x_offset
-     || source->i_y_offset != renderer->last_source.i_y_offset
-     || source->i_visible_width != renderer->last_source.i_visible_width
-     || source->i_visible_height != renderer->last_source.i_visible_height)
-    {
-        float left[PICTURE_PLANE_MAX];
-        float top[PICTURE_PLANE_MAX];
-        float right[PICTURE_PLANE_MAX];
-        float bottom[PICTURE_PLANE_MAX];
-        const struct vlc_gl_interop *interop = renderer->interop;
-        for (unsigned j = 0; j < interop->tex_count; j++)
-        {
-            float scale_w = (float)interop->texs[j].w.num / interop->texs[j].w.den
-                          / renderer->tex_width[j];
-            float scale_h = (float)interop->texs[j].h.num / interop->texs[j].h.den
-                          / renderer->tex_height[j];
-
-            /* Warning: if NPOT is not supported a larger texture is
-               allocated. This will cause right and bottom coordinates to
-               land on the edge of two texels with the texels to the
-               right/bottom uninitialized by the call to
-               glTexSubImage2D. This might cause a green line to appear on
-               the right/bottom of the display.
-               There are two possible solutions:
-               - Manually mirror the edges of the texture.
-               - Add a "-1" when computing right and bottom, however the
-               last row/column might not be displayed at all.
-            */
-            left[j]   = (source->i_x_offset +                       0 ) * scale_w;
-            top[j]    = (source->i_y_offset +                       0 ) * scale_h;
-            right[j]  = (source->i_x_offset + source->i_visible_width ) * scale_w;
-            bottom[j] = (source->i_y_offset + source->i_visible_height) * scale_h;
-        }
-
-        TextureCropForStereo(renderer, left, top, right, bottom);
-        int ret = SetupCoords(renderer, left, top, right, bottom);
-        if (ret != VLC_SUCCESS)
-            return ret;
-
-        renderer->last_source.i_x_offset = source->i_x_offset;
-        renderer->last_source.i_y_offset = source->i_y_offset;
-        renderer->last_source.i_visible_width = source->i_visible_width;
-        renderer->last_source.i_visible_height = source->i_visible_height;
-    }
-    DrawWithShaders(renderer);
+    int ret = vlc_gl_renderer_Draw(vgl->renderer, source);
+    if (ret != VLC_SUCCESS)
+        return ret;
 
-    int ret = vlc_gl_sub_renderer_Draw(vgl->sub_renderer);
+    ret = vlc_gl_sub_renderer_Draw(vgl->sub_renderer);
     if (ret != VLC_SUCCESS)
         return ret;
 
-- 
2.25.0



More information about the vlc-devel mailing list