[vlc-commits] [Git][videolan/vlc][master] 15 commits: opengl: remove useless cast to void

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Mon Oct 25 09:42:57 UTC 2021



Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
dfacd74f by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: remove useless cast to void

The "config" argument is used.

- - - - -
f22ddc78 by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: initialize sampler info on creation

A sampler can be initialized in two ways: with or without an interop.
In both cases, it needs the number of input textures and their sizes.

When initialized from vlc_gl_sampler_NewFromInterop(), the number of
textures and their sizes were set immediately.

However, when initialized from vlc_gl_sampler_NewFromTexture2D(), the
number of textures was re-computed indirectly from the chroma, and the
texture sizes were updated on each picture (even if they are constant).

To unify the behavior, always initialize the number of textures and
their sizes on sampler creation. This paves the way to move the interop
out of the sampler.

- - - - -
3f5a7f5e by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: introduce OpenGL picture and format

This will allow OpenGL filters to handle OpenGL textures directly
(currently, they can only receive the input picture content in GLSL via
vlc_texture()).

- - - - -
877f10e0 by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: use vlc_gl_format in sampler

Replace the private sampler fields related to OpenGL format by a public
instance of the struct vlc_gl_format introduced recently.

This prepares further refactors.

- - - - -
7404ddbf by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: use vlc_gl_picture in sampler

Replace the private sampler fields related to OpenGL format by an
instance of the struct vlc_gl_picture introduced recently.

This prepares further refactors.

- - - - -
013d995b by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: extract importer from sampler

A sampler was responsible for exposing a VLC picture_t as an RGBA
picture, via a vlc_texture() GLSL function.

Extract part of its responsibilities to an "importer", to prepare its
split into two parts:
 1. importing a picture_t to textures (via an interop), handling the
    necessary coordinates transformations;
 2. expose input textures as a single RGBA texel, by handling texture
    access, swizzle and chroma conversion internally.

This will allow OpenGL filters to:
 - bind and read the raw input textures directly;
 - generate chroma conversion GLSL code for any vlc_gl_picture with
   a given vlc_gl_format (not only for input VLC pictures).

As a first step, the importer is internal to the sampler, to split
without impacting the sampler API.

- - - - -
712808c1 by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: move importer out of sampler

This makes the sampler independent of interop and importer. Instead, it
is created from a vlc_gl_format, and receive instances of
vlc_gl_picture.

- - - - -
af808745 by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: compute OpenGL format on filter creation

This will allow to pass the format to the Open() function of OpenGL
filters modules.

- - - - -
dc8af6c2 by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: pass vlc_gl_format to Open()

Pass the input format to the Open() function of OpenGL filter modules.

- - - - -
dd3d2078 by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: pass vlc_gl_picture to draw()

Pass an OpenGL picture to the draw() callback of OpenGL filter modules.

- - - - -
4c41c138 by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: move coords transform to vlc_gl_picture

Now that filters have access to the input vlc_gl_picture, move the
transform to convert from picture coordinates to texture coordinates to
the picture. It is independent of the sampler.

- - - - -
f202ba92 by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: expose vlc_gl_t from vlc_gl_filter

This will allow filters to create their own sampler (a vlc_gl_t instance
is necessary).

- - - - -
5f8aad2e by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: expose glfmt to public filter

This will allow filters to access the format easily from the callbacks.

- - - - -
27adc90f by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: make sampler API public

Some sampler functions were private and only used internally by the
filter engine.

Expose them publicly so that filters could create and manage their own
sampler.

- - - - -
c1770ffc by Romain Vimont at 2021-10-25T09:11:06+00:00
opengl: move sampler out of the filter engine

A sampler now allows to handle chroma conversion from any OpenGL
picture provided by the filter. It can be the input picture directly
(most of the time), or any picture produced by the filter itself.

Therefore, make the sampler a simple helper for filters, independent of
the filter engine.

- - - - -


18 changed files:

- modules/video_filter/deinterlace/glblend.c
- modules/video_output/opengl/Makefile.am
- modules/video_output/opengl/filter.c
- modules/video_output/opengl/filter.h
- modules/video_output/opengl/filter_draw.c
- modules/video_output/opengl/filter_mock.c
- modules/video_output/opengl/filter_priv.h
- modules/video_output/opengl/filters.c
- + modules/video_output/opengl/importer.c
- + modules/video_output/opengl/importer.h
- + modules/video_output/opengl/importer_priv.h
- + modules/video_output/opengl/picture.c
- + modules/video_output/opengl/picture.h
- modules/video_output/opengl/renderer.c
- modules/video_output/opengl/sampler.c
- modules/video_output/opengl/sampler.h
- − modules/video_output/opengl/sampler_priv.h
- modules/video_output/opengl/vout_helper.c


Changes:

=====================================
modules/video_filter/deinterlace/glblend.c
=====================================
@@ -33,8 +33,11 @@
 #include "video_output/opengl/gl_api.h"
 #include "video_output/opengl/gl_common.h"
 #include "video_output/opengl/gl_util.h"
+#include "video_output/opengl/sampler.h"
 
 struct sys {
+    struct vlc_gl_sampler *sampler;
+
     GLuint program_id;
 
     GLuint vbo;
@@ -49,7 +52,8 @@ struct sys {
 };
 
 static int
-Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
+Draw(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+     const struct vlc_gl_input_meta *meta)
 {
     struct sys *sys = filter->sys;
 
@@ -57,12 +61,14 @@ Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
 
     vt->UseProgram(sys->program_id);
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
+    struct vlc_gl_sampler *sampler = sys->sampler;
+    vlc_gl_sampler_SelectPlane(sampler, meta->plane);
+    vlc_gl_sampler_Update(sampler, pic);
     vlc_gl_sampler_Load(sampler);
 
     vt->BindBuffer(GL_ARRAY_BUFFER, sys->vbo);
 
-    if (vlc_gl_sampler_MustRecomputeCoords(sampler))
+    if (pic->mtx_has_changed)
     {
         float coords[] = {
             0, 1,
@@ -72,7 +78,7 @@ Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
         };
 
         /* Transform coordinates in place */
-        vlc_gl_sampler_PicToTexCoords(sampler, 4, coords, coords);
+        vlc_gl_picture_ToTexCoords(pic, 4, coords, coords);
 
         const float data[] = {
             -1,  1, coords[0], coords[1],
@@ -85,7 +91,7 @@ Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
         /* Compute the (normalized) vector representing the _up_ direction in
          * texture coordinates, to take any orientation/flip into account. */
         float direction[2*2];
-        vlc_gl_sampler_ComputeDirectionMatrix(sampler, direction);
+        vlc_gl_picture_ComputeDirectionMatrix(pic, direction);
         sys->up_vector[0] = direction[2];
         sys->up_vector[1] = direction[3];
     }
@@ -101,13 +107,15 @@ Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
     vt->VertexAttribPointer(sys->loc.tex_coords_in, 2, GL_FLOAT, GL_FALSE,
                             stride, (const void *) offset);
 
+    struct vlc_gl_format *glfmt = &sampler->glfmt;
+
     /* If the direction matrix contains a 90° rotation, then the unit vector
      * should be divided by width rather than by height. Since up_vector is
      * always a unit vector with one of its components equal to 0, then we can
      * always devide the horizontal component by width and the vertical
      * component by height. */
-    GLsizei width = sampler->tex_widths[meta->plane];
-    GLsizei height = sampler->tex_heights[meta->plane];
+    GLsizei width = glfmt->tex_widths[meta->plane];
+    GLsizei height = glfmt->tex_heights[meta->plane];
     vt->Uniform2f(sys->loc.one_pixel_up, sys->up_vector[0] / width,
                                          sys->up_vector[1] / height);
 
@@ -122,6 +130,8 @@ Close(struct vlc_gl_filter *filter)
 {
     struct sys *sys = filter->sys;
 
+    vlc_gl_sampler_Delete(sys->sampler);
+
     const opengl_vtable_t *vt = &filter->api->vt;
     vt->DeleteProgram(sys->program_id);
     vt->DeleteBuffers(1, &sys->vbo);
@@ -131,7 +141,7 @@ Close(struct vlc_gl_filter *filter)
 
 static int
 Open(struct vlc_gl_filter *filter, const config_chain_t *config,
-     struct vlc_gl_tex_size *size_out)
+     const struct vlc_gl_format *glfmt, struct vlc_gl_tex_size *size_out)
 {
     (void) config;
     (void) size_out;
@@ -143,13 +153,19 @@ Open(struct vlc_gl_filter *filter, const config_chain_t *config,
     filter->ops = &ops;
     filter->config.filter_planes = true;
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
+    struct vlc_gl_sampler *sampler =
+        vlc_gl_sampler_New(filter->gl, filter->api, glfmt, true);
     if (!sampler)
         return VLC_EGENERIC;
 
     struct sys *sys = filter->sys = malloc(sizeof(*sys));
     if (!sys)
+    {
+        vlc_gl_sampler_Delete(sampler);
         return VLC_EGENERIC;
+    }
+
+    sys->sampler = sampler;
 
     static const char *const VERTEX_SHADER =
         "attribute vec2 vertex_pos;\n"


=====================================
modules/video_output/opengl/Makefile.am
=====================================
@@ -9,13 +9,17 @@ OPENGL_COMMONSOURCES = \
        video_output/opengl/gl_common.h \
        video_output/opengl/gl_util.c \
        video_output/opengl/gl_util.h \
+       video_output/opengl/importer.c \
+       video_output/opengl/importer.h \
+       video_output/opengl/importer_priv.h \
        video_output/opengl/interop.h \
        video_output/opengl/interop.c \
        video_output/opengl/interop_sw.c \
        video_output/opengl/interop_sw.h \
+       video_output/opengl/picture.c \
+       video_output/opengl/picture.h \
        video_output/opengl/sampler.c \
-       video_output/opengl/sampler.h \
-       video_output/opengl/sampler_priv.h
+       video_output/opengl/sampler.h
 
 OPENGL_COMMONCFLAGS = $(LIBPLACEBO_CFLAGS)
 OPENGL_COMMONLIBS = $(LIBPLACEBO_LIBS)


=====================================
modules/video_output/opengl/filter.c
=====================================
@@ -31,23 +31,24 @@
 #include <vlc_modules.h>
 
 #include "gl_api.h"
-#include "sampler_priv.h"
 
-#undef vlc_gl_filter_New
 struct vlc_gl_filter *
-vlc_gl_filter_New(vlc_object_t *parent, const struct vlc_gl_api *api)
+vlc_gl_filter_New(struct vlc_gl_t *gl, const struct vlc_gl_api *api)
 {
-    struct vlc_gl_filter_priv *priv = vlc_object_create(parent, sizeof(*priv));
+    struct vlc_gl_filter_priv *priv = vlc_object_create(gl, sizeof(*priv));
     if (!priv)
         return NULL;
 
-    priv->sampler = NULL;
     priv->size_out.width = 0;
     priv->size_out.height = 0;
 
+    priv->plane_count = 0;
     priv->tex_count = 0;
 
+    priv->has_picture = false;
+
     struct vlc_gl_filter *filter = &priv->filter;
+    filter->gl = gl;
     filter->api = api;
     filter->config.filter_planes = false;
     filter->config.blend = false;
@@ -58,6 +59,9 @@ vlc_gl_filter_New(vlc_object_t *parent, const struct vlc_gl_api *api)
 
     vlc_list_init(&priv->blend_subfilters);
 
+    /* Expose a const pointer to the OpenGL format publicly */
+    filter->glfmt_in = &priv->glfmt_in;
+
     return filter;
 }
 
@@ -68,9 +72,10 @@ ActivateGLFilter(void *func, bool forced, va_list args)
     vlc_gl_filter_open_fn *activate = func;
     struct vlc_gl_filter *filter = va_arg(args, struct vlc_gl_filter *);
     const config_chain_t *config = va_arg(args, config_chain_t *);
+    const struct vlc_gl_format *glfmt = va_arg(args, struct vlc_gl_format *);
     struct vlc_gl_tex_size *size_out = va_arg(args, struct vlc_gl_tex_size *);
 
-    return activate(filter, config, size_out);
+    return activate(filter, config, glfmt, size_out);
 }
 
 #undef vlc_gl_filter_LoadModule
@@ -78,11 +83,12 @@ int
 vlc_gl_filter_LoadModule(vlc_object_t *parent, const char *name,
                          struct vlc_gl_filter *filter,
                          const config_chain_t *config,
+                         const struct vlc_gl_format *glfmt,
                          struct vlc_gl_tex_size *size_out)
 {
     filter->module = vlc_module_load(parent, "opengl filter", name, true,
                                      ActivateGLFilter, filter, config,
-                                     size_out);
+                                     glfmt, size_out);
     if (!filter->module)
         return VLC_EGENERIC;
 
@@ -108,9 +114,6 @@ vlc_gl_filter_Delete(struct vlc_gl_filter *filter)
         vlc_gl_filter_Delete(subfilter);
     }
 
-    if (priv->sampler)
-        vlc_gl_sampler_Delete(priv->sampler);
-
     const opengl_vtable_t *vt = &filter->api->vt;
 
     if (priv->tex_count)


=====================================
modules/video_output/opengl/filter.h
=====================================
@@ -24,7 +24,7 @@
 
 #include <vlc_tick.h>
 
-#include "sampler.h"
+#include "picture.h"
 
 struct vlc_gl_filter;
 
@@ -41,6 +41,7 @@ struct vlc_gl_input_meta {
 typedef int
 vlc_gl_filter_open_fn(struct vlc_gl_filter *filter,
                       const config_chain_t *config,
+                      const struct vlc_gl_format *glfmt,
                       struct vlc_gl_tex_size *size_out);
 
 #define set_callback_opengl_filter(open) \
@@ -54,7 +55,7 @@ struct vlc_gl_filter_ops {
     /**
      * Draw the result of the filter to the current framebuffer
      */
-    int (*draw)(struct vlc_gl_filter *filter,
+    int (*draw)(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
                 const struct vlc_gl_input_meta *meta);
 
     /**
@@ -63,24 +64,6 @@ struct vlc_gl_filter_ops {
     void (*close)(struct vlc_gl_filter *filter);
 };
 
-struct vlc_gl_filter_owner_ops {
-    /**
-     * Get the sampler associated to this filter.
-     *
-     * The instance is lazy-loaded (to avoid creating one for blend filters).
-     * Successive calls to this function for the same filter is guaranteed to
-     * always return the same sampler.
-     *
-     * Important: filter->config must be initialized *before* getting the
-     * sampler, since the sampler behavior may depend on it.
-     *
-     * \param filter the filter
-     * \return sampler the sampler, NULL on error
-     */
-    struct vlc_gl_sampler *
-    (*get_sampler)(struct vlc_gl_filter *filter);
-};
-
 /**
  * OpenGL filter, in charge of a rendering pass.
  */
@@ -88,7 +71,9 @@ struct vlc_gl_filter {
     vlc_object_t obj;
     module_t *module;
 
+    struct vlc_gl_t *gl;
     const struct vlc_gl_api *api;
+    const struct vlc_gl_format *glfmt_in;
 
     struct {
         /**
@@ -127,14 +112,6 @@ struct vlc_gl_filter {
 
     const struct vlc_gl_filter_ops *ops;
     void *sys;
-
-    const struct vlc_gl_filter_owner_ops *owner_ops;
 };
 
-static inline struct vlc_gl_sampler *
-vlc_gl_filter_GetSampler(struct vlc_gl_filter *filter)
-{
-    return filter->owner_ops->get_sampler(filter);
-}
-
 #endif


=====================================
modules/video_output/opengl/filter_draw.c
=====================================
@@ -32,6 +32,7 @@
 #include "gl_api.h"
 #include "gl_common.h"
 #include "gl_util.h"
+#include "sampler.h"
 
 #define DRAW_VFLIP_SHORTTEXT "VFlip the video"
 #define DRAW_VFLIP_LONGTEXT \
@@ -42,6 +43,8 @@
 static const char *const filter_options[] = { "vflip", NULL };
 
 struct sys {
+    struct vlc_gl_sampler *sampler;
+
     GLuint program_id;
 
     GLuint vbo;
@@ -55,7 +58,8 @@ struct sys {
 };
 
 static int
-Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
+Draw(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+     const struct vlc_gl_input_meta *meta)
 {
     (void) meta;
 
@@ -65,12 +69,13 @@ Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
 
     vt->UseProgram(sys->program_id);
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
+    struct vlc_gl_sampler *sampler = sys->sampler;
+    vlc_gl_sampler_Update(sampler, pic);
     vlc_gl_sampler_Load(sampler);
 
     vt->BindBuffer(GL_ARRAY_BUFFER, sys->vbo);
 
-    if (vlc_gl_sampler_MustRecomputeCoords(sampler))
+    if (pic->mtx_has_changed)
     {
         float coords[] = {
             0, sys->vflip ? 0 : 1,
@@ -80,7 +85,7 @@ Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
         };
 
         /* Transform coordinates in place */
-        vlc_gl_sampler_PicToTexCoords(sampler, 4, coords, coords);
+        vlc_gl_picture_ToTexCoords(pic, 4, coords, coords);
 
         const float data[] = {
             -1,  1, coords[0], coords[1],
@@ -113,6 +118,8 @@ Close(struct vlc_gl_filter *filter)
 {
     struct sys *sys = filter->sys;
 
+    vlc_gl_sampler_Delete(sys->sampler);
+
     const opengl_vtable_t *vt = &filter->api->vt;
     vt->DeleteProgram(sys->program_id);
     vt->DeleteBuffers(1, &sys->vbo);
@@ -122,17 +129,23 @@ Close(struct vlc_gl_filter *filter)
 
 static int
 Open(struct vlc_gl_filter *filter, const config_chain_t *config,
-     struct vlc_gl_tex_size *size_out)
+     const struct vlc_gl_format *glfmt, struct vlc_gl_tex_size *size_out)
 {
     (void) size_out;
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
+    struct vlc_gl_sampler *sampler =
+        vlc_gl_sampler_New(filter->gl, filter->api, glfmt, false);
     if (!sampler)
         return VLC_EGENERIC;
 
     struct sys *sys = filter->sys = malloc(sizeof(*sys));
     if (!sys)
+    {
+        vlc_gl_sampler_Delete(sampler);
         return VLC_EGENERIC;
+    }
+
+    sys->sampler = sampler;
 
     static const char *const VERTEX_SHADER_BODY =
         "attribute vec2 vertex_pos;\n"


=====================================
modules/video_output/opengl/filter_mock.c
=====================================
@@ -72,6 +72,7 @@
 #include "gl_api.h"
 #include "gl_common.h"
 #include "gl_util.h"
+#include "sampler.h"
 
 #define MOCK_CFG_PREFIX "mock-"
 
@@ -80,6 +81,8 @@ static const char *const filter_options[] = {
 };
 
 struct sys {
+    struct vlc_gl_sampler *sampler;
+
     GLuint program_id;
 
     GLuint vbo;
@@ -120,10 +123,14 @@ InitMatrix(struct sys *sys, vlc_tick_t pts)
 }
 
 static int
-DrawBlend(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
+DrawBlend(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+          const struct vlc_gl_input_meta *meta)
 {
     struct sys *sys = filter->sys;
 
+    (void) pic;
+    assert(!pic); /* A blend filter should not receive picture */
+
     const opengl_vtable_t *vt = &filter->api->vt;
 
     vt->UseProgram(sys->program_id);
@@ -166,7 +173,8 @@ DrawBlend(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
 }
 
 static int
-DrawMask(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
+DrawMask(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+         const struct vlc_gl_input_meta *meta)
 {
     struct sys *sys = filter->sys;
 
@@ -174,7 +182,8 @@ DrawMask(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
 
     vt->UseProgram(sys->program_id);
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
+    struct vlc_gl_sampler *sampler = sys->sampler;
+    vlc_gl_sampler_Update(sampler, pic);
     vlc_gl_sampler_Load(sampler);
 
     vt->BindBuffer(GL_ARRAY_BUFFER, sys->vbo);
@@ -186,8 +195,7 @@ DrawMask(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
     vt->UniformMatrix4fv(sys->loc.rotation_matrix, 1, GL_FALSE,
                          sys->rotation_matrix);
 
-    const float *mtx = sampler->pic_to_tex_matrix;
-    assert(mtx);
+    const float *mtx = pic->mtx;
 
     /* Expand the 2x3 matrix to 3x3 to store it in a mat3 uniform (for better
      * compatibility). Both are in column-major order. */
@@ -203,22 +211,27 @@ DrawMask(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
 }
 
 static int
-DrawPlane(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
+DrawPlane(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+          const struct vlc_gl_input_meta *meta)
 {
+    (void) pic; /* TODO not used yet */
+
     struct sys *sys = filter->sys;
 
     const opengl_vtable_t *vt = &filter->api->vt;
 
     vt->UseProgram(sys->program_id);
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
+    struct vlc_gl_sampler *sampler = sys->sampler;
+    vlc_gl_sampler_Update(sampler, pic);
+    vlc_gl_sampler_SelectPlane(sampler, meta->plane);
     vlc_gl_sampler_Load(sampler);
 
     vt->Uniform1f(sys->loc.offset, 0.02 * meta->plane);
 
     vt->BindBuffer(GL_ARRAY_BUFFER, sys->vbo);
 
-    if (vlc_gl_sampler_MustRecomputeCoords(sampler))
+    if (pic->mtx_has_changed)
     {
         float coords[] = {
             0, 1,
@@ -228,7 +241,7 @@ DrawPlane(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
         };
 
         /* Transform coordinates in place */
-        vlc_gl_sampler_PicToTexCoords(sampler, 4, coords, coords);
+        vlc_gl_picture_ToTexCoords(pic, 4, coords, coords);
 
         const float data[] = {
             -1,  1, coords[0], coords[1],
@@ -261,6 +274,9 @@ Close(struct vlc_gl_filter *filter)
 {
     struct sys *sys = filter->sys;
 
+    if (sys->sampler)
+        vlc_gl_sampler_Delete(sys->sampler);
+
     const opengl_vtable_t *vt = &filter->api->vt;
     vt->DeleteProgram(sys->program_id);
     vt->DeleteBuffers(1, &sys->vbo);
@@ -358,15 +374,18 @@ InitBlend(struct vlc_gl_filter *filter)
 }
 
 static int
-InitMask(struct vlc_gl_filter *filter)
+InitMask(struct vlc_gl_filter *filter, const struct vlc_gl_format *glfmt)
 {
     struct sys *sys = filter->sys;
     const opengl_vtable_t *vt = &filter->api->vt;
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
+    struct vlc_gl_sampler *sampler =
+        vlc_gl_sampler_New(filter->gl, filter->api, glfmt, false);
     if (!sampler)
         return VLC_EGENERIC;
 
+    sys->sampler = sampler;
+
     static const char *const VERTEX_SHADER_BODY =
         "attribute vec2 vertex_pos;\n"
         "uniform mat4 rotation_matrix;\n"
@@ -459,18 +478,20 @@ InitMask(struct vlc_gl_filter *filter)
 }
 
 static int
-InitPlane(struct vlc_gl_filter *filter)
+InitPlane(struct vlc_gl_filter *filter, const struct vlc_gl_format *glfmt)
 {
     struct sys *sys = filter->sys;
     const opengl_vtable_t *vt = &filter->api->vt;
 
-    /* Must be initialized before calling vlc_gl_filter_GetSampler() */
     filter->config.filter_planes = true;
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
-    if (!sampler)
+    struct vlc_gl_sampler *sampler =
+        vlc_gl_sampler_New(filter->gl, filter->api, glfmt, true);
+    if (!sys->sampler)
         return VLC_EGENERIC;
 
+    sys->sampler = sampler;
+
     static const char *const VERTEX_SHADER_BODY =
         "attribute vec2 vertex_pos;\n"
         "attribute vec2 tex_coords_in;\n"
@@ -549,10 +570,8 @@ InitPlane(struct vlc_gl_filter *filter)
 
 static int
 Open(struct vlc_gl_filter *filter, const config_chain_t *config,
-     struct vlc_gl_tex_size *size_out)
+     const struct vlc_gl_format *glfmt, struct vlc_gl_tex_size *size_out)
 {
-    (void) config;
-
     config_ChainParse(filter, MOCK_CFG_PREFIX, filter_options, config);
 
     bool mask = var_InheritBool(filter, MOCK_CFG_PREFIX "mask");
@@ -565,11 +584,13 @@ Open(struct vlc_gl_filter *filter, const config_chain_t *config,
     if (!sys)
         return VLC_EGENERIC;
 
+    sys->sampler = NULL;
+
     int ret;
     if (plane)
-        ret = InitPlane(filter);
+        ret = InitPlane(filter, glfmt);
     else if (mask)
-        ret = InitMask(filter);
+        ret = InitMask(filter, glfmt);
     else
         ret = InitBlend(filter);
 


=====================================
modules/video_output/opengl/filter_priv.h
=====================================
@@ -27,7 +27,6 @@
 #include <vlc_picture.h>
 
 #include "filter.h"
-#include "sampler.h"
 
 struct vlc_gl_filter_priv {
     struct vlc_gl_filter filter;
@@ -36,8 +35,13 @@ struct vlc_gl_filter_priv {
      * filter */
     struct vlc_gl_tex_size size_out;
 
-    /* Only meaningful for non-blend filters { */
-    struct vlc_gl_sampler *sampler; /* owned */
+    struct vlc_gl_format glfmt_in;
+
+    /* Describe the output planes, independently of whether textures are
+     * created for this filter (the last filter does not own any textures). */
+    unsigned plane_count;
+    GLsizei plane_widths[PICTURE_PLANE_MAX];
+    GLsizei plane_heights[PICTURE_PLANE_MAX];
 
     /* owned (this filter must delete it) */
     GLuint framebuffers_out[PICTURE_PLANE_MAX];
@@ -53,19 +57,13 @@ struct vlc_gl_filter_priv {
     GLuint renderbuffer_msaa; /* owned (attached to framebuffer_msaa) */
     /* } */
 
-    /* For lazy-loading sampler */
-    struct vlc_gl_filters *filters; /* weak reference to the container */
-
-    /* Previous filter to construct the expected sampler. It is necessary
-     * because owner_ops->get_sampler() may be called during the Open(), while
-     * the filter is not added to the filter chain yet. */
-    struct vlc_gl_filter_priv *prev_filter;
-
     struct vlc_list node; /**< node of vlc_gl_filters.list */
 
     /* Blend filters are attached to their non-blend "parent" instead of the
      * filter chain to simplify the rendering code */
     struct vlc_list blend_subfilters; /**< list of vlc_gl_filter_priv.node */
+
+    bool has_picture;
 };
 
 static inline struct vlc_gl_filter_priv *
@@ -75,16 +73,16 @@ vlc_gl_filter_PRIV(struct vlc_gl_filter *filter)
 }
 
 struct vlc_gl_filter *
-vlc_gl_filter_New(vlc_object_t *parent, const struct vlc_gl_api *api);
-#define vlc_gl_filter_New(o, a) vlc_gl_filter_New(VLC_OBJECT(o), a)
+vlc_gl_filter_New(struct vlc_gl_t *gl, const struct vlc_gl_api *api);
 
 int
 vlc_gl_filter_LoadModule(vlc_object_t *parent, const char *name,
                          struct vlc_gl_filter *filter,
                          const config_chain_t *config,
+                         const struct vlc_gl_format *glfmt,
                          struct vlc_gl_tex_size *size_out);
-#define vlc_gl_filter_LoadModule(o, a, b, c, d) \
-    vlc_gl_filter_LoadModule(VLC_OBJECT(o), a, b, c, d)
+#define vlc_gl_filter_LoadModule(o, a, b, c, d, e) \
+    vlc_gl_filter_LoadModule(VLC_OBJECT(o), a, b, c, d, e)
 
 void
 vlc_gl_filter_Delete(struct vlc_gl_filter *filter);


=====================================
modules/video_output/opengl/filters.c
=====================================
@@ -29,7 +29,7 @@
 #include <vlc_list.h>
 
 #include "filter_priv.h"
-#include "sampler_priv.h"
+#include "importer_priv.h"
 
 /* The filter chain contains the sequential list of filters.
  *
@@ -116,10 +116,10 @@ struct vlc_gl_filters {
     const struct vlc_gl_api *api;
 
     /**
-     * Interop to use for the sampler of the first filter of the chain,
-     * the one which uses the picture_t as input.
+     * Interop to use for the input picture.
      */
     struct vlc_gl_interop *interop;
+    struct vlc_gl_importer *importer;
 
     struct vlc_list list; /**< list of vlc_gl_filter.node */
 
@@ -144,6 +144,13 @@ vlc_gl_filters_New(struct vlc_gl_t *gl, const struct vlc_gl_api *api,
     if (!filters)
         return NULL;
 
+    filters->importer = vlc_gl_importer_New(interop);
+    if (!filters->importer)
+    {
+        free(filters);
+        return NULL;
+    }
+
     filters->gl = gl;
     filters->api = api;
     filters->interop = interop;
@@ -165,6 +172,7 @@ vlc_gl_filters_Delete(struct vlc_gl_filters *filters)
         vlc_gl_filter_Delete(filter);
     }
 
+    vlc_gl_importer_Delete(filters->importer);
     free(filters);
 }
 
@@ -209,20 +217,18 @@ InitFramebuffersOut(struct vlc_gl_filter_priv *priv)
     struct vlc_gl_filter *filter = &priv->filter;
     if (filter->config.filter_planes)
     {
-        struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
-        if (!sampler)
-            return VLC_EGENERIC;
+        struct vlc_gl_format *glfmt = &priv->glfmt_in;
 
-        priv->tex_count = sampler->tex_count;
+        priv->tex_count = glfmt->tex_count;
         vt->GenFramebuffers(priv->tex_count, priv->framebuffers_out);
         vt->GenTextures(priv->tex_count, priv->textures_out);
 
-        for (unsigned i = 0; i < sampler->tex_count; ++i)
+        for (unsigned i = 0; i < glfmt->tex_count; ++i)
         {
-            priv->tex_widths[i] = priv->size_out.width * sampler->tex_widths[i]
-                                / sampler->tex_widths[0];
-            priv->tex_heights[i] = priv->size_out.height * sampler->tex_heights[i]
-                                 / sampler->tex_heights[0];
+            memcpy(priv->tex_widths, priv->plane_widths,
+                   priv->tex_count * sizeof(*priv->tex_widths));
+            memcpy(priv->tex_heights, priv->plane_heights,
+                   priv->tex_count * sizeof(*priv->tex_heights));
             /* Init one framebuffer and texture for each plane */
             int ret =
                 InitPlane(priv, i, priv->tex_widths[i], priv->tex_heights[i]);
@@ -277,45 +283,6 @@ InitFramebufferMSAA(struct vlc_gl_filter_priv *priv, unsigned msaa_level)
     return VLC_SUCCESS;
 }
 
-static struct vlc_gl_sampler *
-GetSampler(struct vlc_gl_filter *filter)
-{
-    struct vlc_gl_filter_priv *priv = vlc_gl_filter_PRIV(filter);
-    if (priv->sampler)
-        /* already initialized */
-        return priv->sampler;
-
-    struct vlc_gl_filters *filters = priv->filters;
-    struct vlc_gl_filter_priv *prev_filter = priv->prev_filter;
-
-    bool expose_planes = filter->config.filter_planes;
-    struct vlc_gl_sampler *sampler;
-    if (!priv->prev_filter)
-        sampler = vlc_gl_sampler_NewFromInterop(filters->interop,
-                                                expose_planes);
-    else
-    {
-        video_format_t fmt;
-
-        /* If the previous filter operated on planes, then its output chroma is
-         * the same as its input chroma. Otherwise, it's RGBA. */
-        vlc_fourcc_t chroma = prev_filter->filter.config.filter_planes
-                            ? prev_filter->sampler->fmt.i_chroma
-                            : VLC_CODEC_RGBA;
-
-        video_format_Init(&fmt, chroma);
-        fmt.i_width = fmt.i_visible_width = prev_filter->size_out.width;
-        fmt.i_height = fmt.i_visible_height = prev_filter->size_out.height;
-
-        sampler = vlc_gl_sampler_NewFromTexture2D(filters->gl, filters->api,
-                                                  &fmt, expose_planes);
-    }
-
-    priv->sampler = sampler;
-
-    return sampler;
-}
-
 struct vlc_gl_filter *
 vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
                       const config_chain_t *config)
@@ -327,6 +294,7 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
     struct vlc_gl_filter_priv *priv = vlc_gl_filter_PRIV(filter);
 
     struct vlc_gl_tex_size size_in;
+    struct vlc_gl_format *glfmt = &priv->glfmt_in;
 
     struct vlc_gl_filter_priv *prev_filter =
         vlc_list_last_entry_or_null(&filters->list, struct vlc_gl_filter_priv,
@@ -335,26 +303,41 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
     {
         size_in.width = filters->interop->fmt_out.i_visible_width;
         size_in.height = filters->interop->fmt_out.i_visible_height;
+
+        assert(filters->importer);
+        *glfmt = filters->importer->glfmt;
     }
     else
     {
         size_in = prev_filter->size_out;
-    }
 
-    priv->filters = filters;
-    priv->prev_filter = prev_filter;
+        /* If the previous filter operated on planes, then its output chroma is
+         * the same as its input chroma. Otherwise, it's RGBA. */
+        vlc_fourcc_t chroma = prev_filter->filter.config.filter_planes
+                            ? prev_filter->glfmt_in.fmt.i_chroma
+                            : VLC_CODEC_RGBA;
 
-    static const struct vlc_gl_filter_owner_ops owner_ops = {
-        .get_sampler = GetSampler,
-    };
-    filter->owner_ops = &owner_ops;
+        video_format_t *fmt = &glfmt->fmt;
+        video_format_Init(fmt, chroma);
+        fmt->i_width = fmt->i_visible_width = prev_filter->size_out.width;
+        fmt->i_height = fmt->i_visible_height = prev_filter->size_out.height;
+
+        glfmt->tex_target = GL_TEXTURE_2D;
+        glfmt->tex_count = prev_filter->plane_count;
+
+        size_t size = glfmt->tex_count * sizeof(GLsizei);
+        memcpy(glfmt->tex_widths, prev_filter->plane_widths, size);
+        memcpy(glfmt->tex_heights, prev_filter->plane_heights, size);
+        memcpy(glfmt->visible_widths, prev_filter->plane_widths, size);
+        memcpy(glfmt->visible_heights, prev_filter->plane_heights, size);
+    }
 
     /* By default, the output size is the same as the input size. The filter
      * may change it during its Open(). */
     priv->size_out = size_in;
 
     int ret = vlc_gl_filter_LoadModule(filters->gl, name, filter, config,
-                                       &priv->size_out);
+                                       glfmt, &priv->size_out);
     if (ret != VLC_SUCCESS)
     {
         /* Creation failed, do not call close() */
@@ -374,15 +357,6 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
     /* A filter operating on planes may not use anti-aliasing. */
     assert(!filter->config.filter_planes || !filter->config.msaa_level);
 
-    /* A blend filter may not read its input, so it is an error if a sampler
-     * has been requested.
-     *
-     * We assert it here instead of in vlc_gl_filter_GetSampler() because the
-     * filter implementation may set the "blend" flag after it get the sampler
-     * in its Open() function.
-     */
-    assert(!filter->config.blend || !priv->sampler);
-
     if (filter->config.blend)
     {
         if (!prev_filter || prev_filter->filter.config.filter_planes)
@@ -407,12 +381,24 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
     }
     else
     {
-        /* Make sure the sampler of non-blend filters is initialized */
-        struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
-        if (!sampler)
+        if (filter->config.filter_planes)
+        {
+            priv->plane_count = glfmt->tex_count;
+            for (unsigned i = 0; i < glfmt->tex_count; ++i)
+            {
+                priv->plane_widths[i] = priv->size_out.width
+                                      * glfmt->tex_widths[i]
+                                      / glfmt->tex_widths[0];
+                priv->plane_heights[i] = priv->size_out.height
+                                       * glfmt->tex_heights[i]
+                                       / glfmt->tex_heights[0];
+            }
+        }
+        else
         {
-            vlc_gl_filter_Delete(filter);
-            return NULL;
+            priv->plane_count = 1;
+            priv->plane_widths[0] = priv->size_out.width;
+            priv->plane_heights[0] = priv->size_out.height;
         }
 
         /* Append to the main filter list */
@@ -512,21 +498,28 @@ vlc_gl_filters_UpdatePicture(struct vlc_gl_filters *filters,
 {
     assert(!vlc_list_is_empty(&filters->list));
 
+    struct vlc_gl_importer *importer = filters->importer;
+    int ret = vlc_gl_importer_Update(importer, picture);
+    if (ret != VLC_SUCCESS)
+        return ret;
+
+    filters->pic.pts = picture->date;
+
     struct vlc_gl_filter_priv *first_filter =
         vlc_list_first_entry_or_null(&filters->list, struct vlc_gl_filter_priv,
                                      node);
 
     assert(first_filter);
+    first_filter->has_picture = true;
 
-    filters->pic.pts = picture->date;
-
-    return vlc_gl_sampler_UpdatePicture(first_filter->sampler, picture);
+    return VLC_SUCCESS;
 }
 
 int
 vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
 {
     const opengl_vtable_t *vt = &filters->api->vt;
+    struct vlc_gl_importer *importer = filters->importer;
 
     GLint value;
     vt->GetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &value);
@@ -537,6 +530,9 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
         .plane = 0,
     };
 
+    struct vlc_gl_picture direct_pic;
+    const struct vlc_gl_picture *pic;
+
     struct vlc_gl_filter_priv *priv;
     vlc_list_foreach(priv, &filters->list, node)
     {
@@ -545,16 +541,22 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
                                         struct vlc_gl_filter_priv, node);
         if (previous)
         {
-            /* Read from the output of the previous filter */
-            int ret = vlc_gl_sampler_UpdateTextures(priv->sampler,
-                                                    previous->textures_out,
-                                                    previous->tex_widths,
-                                                    previous->tex_heights);
-            if (ret != VLC_SUCCESS)
-            {
-                msg_Err(filters->gl, "Could not update sampler texture");
-                return ret;
-            }
+            memcpy(direct_pic.textures, previous->textures_out,
+                   previous->tex_count * sizeof(*direct_pic.textures));
+            memcpy(direct_pic.mtx, MATRIX2x3_IDENTITY,
+                   sizeof(MATRIX2x3_IDENTITY));
+            /* The transform never changes (except for the first picture, where
+             * it is defined for the first time) */
+            direct_pic.mtx_has_changed = !priv->has_picture;
+
+            priv->has_picture = true;
+
+            pic = &direct_pic;
+        }
+        else
+        {
+            assert(importer);
+            pic = &importer->pic;
         }
 
         struct vlc_gl_filter *filter = &priv->filter;
@@ -572,8 +574,7 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
                 assert(!vlc_list_is_last(&priv->node, &filters->list));
                 vt->Viewport(0, 0, priv->tex_widths[i], priv->tex_heights[i]);
 
-                vlc_gl_sampler_SelectPlane(priv->sampler, i);
-                int ret = filter->ops->draw(filter, &meta);
+                int ret = filter->ops->draw(filter, pic, &meta);
                 if (ret != VLC_SUCCESS)
                     return ret;
             }
@@ -601,7 +602,7 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
                 vt->Viewport(0, 0, priv->tex_widths[0], priv->tex_heights[0]);
 
             meta.plane = 0;
-            int ret = filter->ops->draw(filter, &meta);
+            int ret = filter->ops->draw(filter, pic, &meta);
             if (ret != VLC_SUCCESS)
                 return ret;
 
@@ -614,7 +615,7 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
                 vt->BindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_fb);
 
                 struct vlc_gl_filter *subfilter = &subfilter_priv->filter;
-                ret = subfilter->ops->draw(subfilter, &meta);
+                ret = subfilter->ops->draw(subfilter, NULL, &meta);
                 if (ret != VLC_SUCCESS)
                     return ret;
             }


=====================================
modules/video_output/opengl/importer.c
=====================================
@@ -0,0 +1,424 @@
+/*****************************************************************************
+ * importer.c
+ *****************************************************************************
+ * Copyright (C) 2020 VLC authors and VideoLAN
+ *
+ * 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 <vlc_common.h>
+
+#include "importer_priv.h"
+
+#include "gl_api.h"
+#include "gl_common.h"
+#include "gl_util.h"
+#include "interop.h"
+#include "picture.h"
+
+static const GLfloat *
+GetTransformMatrix(const struct vlc_gl_interop *interop)
+{
+    const GLfloat *tm = NULL;
+    if (interop && interop->ops && interop->ops->get_transform_matrix)
+        tm = interop->ops->get_transform_matrix(interop);
+    return tm;
+}
+
+static void
+InitOrientationMatrix(float matrix[static 2*3], video_orientation_t orientation)
+{
+/**
+ * / C0R0  C1R0  C3R0 \
+ * \ C0R1  C1R1  C3R1 /
+ *
+ * (note that in memory, the matrix is stored in column-major order)
+ */
+#define MATRIX_SET(C0R0, C1R0, C3R0, \
+                   C0R1, C1R1, C3R1) \
+    matrix[0*2 + 0] = C0R0; \
+    matrix[1*2 + 0] = C1R0; \
+    matrix[2*2 + 0] = C3R0; \
+    matrix[0*2 + 1] = C0R1; \
+    matrix[1*2 + 1] = C1R1; \
+    matrix[2*2 + 1] = C3R1;
+
+    /**
+     * The following schemas show how the video picture is oriented in the
+     * texture, according to the "orientation" value:
+     *
+     *     video         texture
+     *    picture        storage
+     *
+     *     1---2          2---3
+     *     |   |   --->   |   |
+     *     4---3          1---4
+     *
+     * In addition, they show how the orientation transforms video picture
+     * coordinates axis (x,y) into texture axis (X,Y):
+     *
+     *   y         --->         X
+     *   |                      |
+     *   +---x              Y---+
+     *
+     * The resulting coordinates undergo the reverse of the transformation
+     * applied to the axis, so expressing (x,y) in terms of (X,Y) gives the
+     * orientation matrix coefficients.
+     */
+
+    switch (orientation) {
+        case ORIENT_ROTATED_90:
+            /**
+             *     1---2          2---3
+             *   y |   |   --->   |   | X
+             *   | 4---3          1---4 |
+             *   +---x              Y---+
+             *
+             *          x = 1-Y
+             *          y = X
+             */
+                     /* X  Y  1 */
+            MATRIX_SET( 0,-1, 1, /* 1-Y */
+                        1, 0, 0) /* X */
+            break;
+        case ORIENT_ROTATED_180:
+            /**
+             *                      X---+
+             *     1---2          3---4 |
+             *   y |   |   --->   |   | Y
+             *   | 4---3          2---1
+             *   +---x
+             *
+             *          x = 1-X
+             *          y = 1-Y
+             */
+                     /* X  Y  1 */
+            MATRIX_SET(-1, 0, 1, /* 1-X */
+                        0,-1, 1) /* 1-Y */
+            break;
+        case ORIENT_ROTATED_270:
+            /**
+             *                    +---Y
+             *     1---2          | 4---1
+             *   y |   |   --->   X |   |
+             *   | 4---3            3---2
+             *   +---x
+             *
+             *          x = Y
+             *          y = 1-X
+             */
+                     /* X  Y  1 */
+            MATRIX_SET( 0, 1, 0, /* Y */
+                       -1, 0, 1) /* 1-X */
+            break;
+        case ORIENT_HFLIPPED:
+            /**
+             *     1---2          2---1
+             *   y |   |   --->   |   | Y
+             *   | 4---3          3---4 |
+             *   +---x              X---+
+             *
+             *          x = 1-X
+             *          y = Y
+             */
+                     /* X  Y  1 */
+            MATRIX_SET(-1, 0, 1, /* 1-X */
+                        0, 1, 0) /* Y */
+            break;
+        case ORIENT_VFLIPPED:
+            /**
+             *                    +---X
+             *     1---2          | 4---3
+             *   y |   |   --->   Y |   |
+             *   | 4---3            1---2
+             *   +---x
+             *
+             *          x = X
+             *          y = 1-Y
+             */
+                     /* X  Y  1 */
+            MATRIX_SET( 1, 0, 0, /* X */
+                        0,-1, 1) /* 1-Y */
+            break;
+        case ORIENT_TRANSPOSED:
+            /**
+             *                      Y---+
+             *     1---2          1---4 |
+             *   y |   |   --->   |   | X
+             *   | 4---3          2---3
+             *   +---x
+             *
+             *          x = 1-Y
+             *          y = 1-X
+             */
+                     /* X  Y  1 */
+            MATRIX_SET( 0,-1, 1, /* 1-Y */
+                       -1, 0, 1) /* 1-X */
+            break;
+        case ORIENT_ANTI_TRANSPOSED:
+            /**
+             *     1---2            3---2
+             *   y |   |   --->   X |   |
+             *   | 4---3          | 4---1
+             *   +---x            +---Y
+             *
+             *          x = Y
+             *          y = X
+             */
+                     /* X  Y  1 */
+            MATRIX_SET( 0, 1, 0, /* Y */
+                        1, 0, 0) /* X */
+            break;
+        default:
+            break;
+    }
+}
+
+struct vlc_gl_importer *
+vlc_gl_importer_New(struct vlc_gl_interop *interop)
+{
+    assert(interop);
+
+    struct vlc_gl_importer *importer = malloc(sizeof(*importer));
+    if (!importer)
+        return NULL;
+
+    importer->interop = interop;
+    importer->api = interop->api;
+    importer->vt = &interop->api->vt;
+
+    importer->mtx_transform_defined = false;
+    importer->pic_mtx_defined = false;
+
+    struct vlc_gl_format *glfmt = &importer->glfmt;
+    struct vlc_gl_picture *pic = &importer->pic;
+
+    /* Formats with palette are not supported. This also allows to copy
+     * video_format_t without possibility of failure. */
+    assert(!interop->fmt_out.p_palette);
+
+    glfmt->fmt = interop->fmt_out;
+    glfmt->tex_target = interop->tex_target;
+    glfmt->tex_count = interop->tex_count;
+
+    /* This matrix may be updated on new pictures */
+    memcpy(&importer->mtx_coords_map, MATRIX2x3_IDENTITY,
+           sizeof(MATRIX2x3_IDENTITY));
+
+    InitOrientationMatrix(importer->mtx_orientation, glfmt->fmt.orientation);
+
+    /* Texture size */
+    for (unsigned j = 0; j < interop->tex_count; j++) {
+        GLsizei w = interop->fmt_out.i_visible_width  * interop->texs[j].w.num
+                  / interop->texs[j].w.den;
+        GLsizei h = interop->fmt_out.i_visible_height * interop->texs[j].h.num
+                  / interop->texs[j].h.den;
+        glfmt->visible_widths[j] = w;
+        glfmt->visible_heights[j] = h;
+        if (interop->api->supports_npot) {
+            glfmt->tex_widths[j]  = w;
+            glfmt->tex_heights[j] = h;
+        } else {
+            glfmt->tex_widths[j]  = vlc_align_pot(w);
+            glfmt->tex_heights[j] = vlc_align_pot(h);
+        }
+    }
+
+    if (!interop->handle_texs_gen)
+    {
+        int ret = vlc_gl_interop_GenerateTextures(interop, glfmt->tex_widths,
+                                                  glfmt->tex_heights,
+                                                  pic->textures);
+        if (ret != VLC_SUCCESS)
+        {
+            free(importer);
+            return NULL;
+        }
+    }
+
+    return importer;
+}
+
+void
+vlc_gl_importer_Delete(struct vlc_gl_importer *importer)
+{
+    struct vlc_gl_interop *interop = importer->interop;
+    if (interop && !interop->handle_texs_gen)
+    {
+        const opengl_vtable_t *vt = interop->vt;
+        vt->DeleteTextures(interop->tex_count, importer->pic.textures);
+    }
+
+    free(importer);
+}
+
+/**
+ * Compute out = a * b, as if the 2x3 matrices were expanded to 3x3 with
+ *  [0 0 1] as the last row.
+ */
+static void
+MatrixMultiply(float out[static 2*3],
+               const float a[static 2*3], const float b[static 2*3])
+{
+    /* All matrices are stored in column-major order. */
+    for (unsigned i = 0; i < 3; ++i)
+        for (unsigned j = 0; j < 2; ++j)
+            out[i*2+j] = a[0*2+j] * b[i*2+0]
+                       + a[1*2+j] * b[i*2+1];
+
+    /* Multiply the last implicit row [0 0 1] of b, expanded to 3x3 */
+    out[2*2+0] += a[2*2+0];
+    out[2*2+1] += a[2*2+1];
+}
+
+static void
+UpdatePictureMatrix(struct vlc_gl_importer *importer)
+{
+    float tmp[2*3];
+
+    struct vlc_gl_picture *pic = &importer->pic;
+
+    float *out = importer->mtx_transform_defined ? tmp : pic->mtx;
+    /* out = mtx_coords_map * mtx_orientation */
+    MatrixMultiply(out, importer->mtx_coords_map, importer->mtx_orientation);
+
+    if (importer->mtx_transform_defined)
+        /* mtx_all = mtx_transform * tmp */
+        MatrixMultiply(pic->mtx, importer->mtx_transform, tmp);
+}
+
+int
+vlc_gl_importer_Update(struct vlc_gl_importer *importer, picture_t *picture)
+{
+    struct vlc_gl_interop *interop = importer->interop;
+    struct vlc_gl_format *glfmt = &importer->glfmt;
+    struct vlc_gl_picture *pic = &importer->pic;
+
+    const video_format_t *source = &picture->format;
+
+    bool mtx_changed = false;
+
+    if (!importer->pic_mtx_defined
+     || source->i_x_offset != importer->last_source.i_x_offset
+     || source->i_y_offset != importer->last_source.i_y_offset
+     || source->i_visible_width != importer->last_source.i_visible_width
+     || source->i_visible_height != importer->last_source.i_visible_height)
+    {
+        memset(importer->mtx_coords_map, 0, sizeof(importer->mtx_coords_map));
+
+        /* The transformation is the same for all planes, even with power-of-two
+         * textures. */
+        float scale_w = glfmt->tex_widths[0];
+        float scale_h = glfmt->tex_heights[0];
+
+        /* 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.
+        */
+        float left   = (source->i_x_offset +                       0 ) / scale_w;
+        float top    = (source->i_y_offset +                       0 ) / scale_h;
+        float right  = (source->i_x_offset + source->i_visible_width ) / scale_w;
+        float bottom = (source->i_y_offset + source->i_visible_height) / scale_h;
+
+        /**
+         * This matrix converts from picture coordinates (in range [0; 1])
+         * to textures coordinates where the picture is actually stored
+         * (removing paddings).
+         *
+         *        texture           (in texture coordinates)
+         *       +----------------+--- 0.0
+         *       |                |
+         *       |  +---------+---|--- top
+         *       |  | picture |   |
+         *       |  +---------+---|--- bottom
+         *       |  .         .   |
+         *       |  .         .   |
+         *       +----------------+--- 1.0
+         *       |  .         .   |
+         *      0.0 left  right  1.0  (in texture coordinates)
+         *
+         * In particular:
+         *  - (0.0, 0.0) is mapped to (left, top)
+         *  - (1.0, 1.0) is mapped to (right, bottom)
+         *
+         * This is an affine 2D transformation, so the input coordinates
+         * are given as a 3D vector in the form (x, y, 1), and the output
+         * is (x', y').
+         *
+         * The paddings are l (left), r (right), t (top) and b (bottom).
+         *
+         *      matrix = / (r-l)   0     l \
+         *               \   0   (b-t)   t /
+         *
+         * It is stored in column-major order.
+         */
+        float *matrix = importer->mtx_coords_map;
+#define COL(x) (x*2)
+#define ROW(x) (x)
+        matrix[COL(0) + ROW(0)] = right - left;
+        matrix[COL(1) + ROW(1)] = bottom - top;
+        matrix[COL(2) + ROW(0)] = left;
+        matrix[COL(2) + ROW(1)] = top;
+#undef COL
+#undef ROW
+
+        mtx_changed = true;
+
+        importer->last_source.i_x_offset = source->i_x_offset;
+        importer->last_source.i_y_offset = source->i_y_offset;
+        importer->last_source.i_visible_width = source->i_visible_width;
+        importer->last_source.i_visible_height = source->i_visible_height;
+    }
+
+    /* Update the texture */
+    int ret = interop->ops->update_textures(interop, pic->textures,
+                                            glfmt->visible_widths,
+                                            glfmt->visible_heights, picture,
+                                            NULL);
+
+    const float *tm = GetTransformMatrix(interop);
+    if (tm) {
+        memcpy(importer->mtx_transform, tm, sizeof(importer->mtx_transform));
+        importer->mtx_transform_defined = true;
+        mtx_changed = true;
+    }
+    else if (importer->mtx_transform_defined)
+    {
+        importer->mtx_transform_defined = false;
+        mtx_changed = true;
+    }
+
+    if (!importer->pic_mtx_defined || mtx_changed)
+    {
+        UpdatePictureMatrix(importer);
+        importer->pic_mtx_defined = true;
+        pic->mtx_has_changed = true;
+    }
+    else
+        pic->mtx_has_changed = false;
+
+    return ret;
+}


=====================================
modules/video_output/opengl/importer.h
=====================================
@@ -0,0 +1,46 @@
+/*****************************************************************************
+ * importer.h
+ *****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * 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_GL_IMPORTER_H
+#define VLC_GL_IMPORTER_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_picture.h>
+#include "interop.h"
+
+/**
+ * An importer uses an interop to convert picture_t to a valid vlc_gl_picture,
+ * with all necessary transformations computed.
+ */
+struct vlc_gl_importer;
+
+struct vlc_gl_importer *
+vlc_gl_importer_New(struct vlc_gl_interop *interop);
+
+void
+vlc_gl_importer_Delete(struct vlc_gl_importer *importer);
+
+int
+vlc_gl_importer_Update(struct vlc_gl_importer *importer, picture_t *picture);
+
+#endif


=====================================
modules/video_output/opengl/importer_priv.h
=====================================
@@ -0,0 +1,78 @@
+/*****************************************************************************
+ * importer_priv.h
+ *****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * 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_GL_IMPORTER_PRIV_H
+#define VLC_GL_IMPORTER_PRIV_H
+
+#include "importer.h"
+
+#include "gl_api.h"
+#include "gl_common.h"
+#include "gl_util.h"
+#include "interop.h"
+#include "picture.h"
+
+struct vlc_gl_importer {
+    struct vlc_gl_format glfmt;
+    struct vlc_gl_interop *interop;
+
+    /* For convenience, same as interop->api and interop->api->vt */
+    const struct vlc_gl_api *api;
+    const opengl_vtable_t *vt;
+
+    struct vlc_gl_picture pic;
+
+    struct {
+        unsigned int i_x_offset;
+        unsigned int i_y_offset;
+        unsigned int i_visible_width;
+        unsigned int i_visible_height;
+    } last_source;
+
+    /* All matrices below are stored in column-major order. */
+
+    float mtx_orientation[2*3];
+    float mtx_coords_map[2*3];
+
+    float mtx_transform[2*3];
+    bool mtx_transform_defined;
+
+    /**
+     * The complete transformation matrix is stored in pic.mtx.
+     *
+     * tex_coords =  pic_to_tex × pic_coords
+     *
+     *  / tex_x \    / a b c \    / pic_x \
+     *  \ tex_y / =  \ d e f /  × | pic_y |
+     *                            \   1   /
+     *
+     * Semantically, it represents the result of:
+     *
+     *     get_transform_matrix() * mtx_coords_map * mtx_orientation
+     *
+     * (The intermediate matrices are implicitly expanded to 3x3 with [0 0 1]
+     * as the last row.)
+     *
+     * It is stored in column-major order: [a, d, b, e, c, f].
+     */
+    bool pic_mtx_defined;
+};
+
+#endif


=====================================
modules/video_output/opengl/picture.c
=====================================
@@ -0,0 +1,82 @@
+/*****************************************************************************
+ * picture.c
+ *****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * 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 "picture.h"
+
+void
+vlc_gl_picture_ToTexCoords(const struct vlc_gl_picture *pic,
+                           unsigned coords_count, const float *pic_coords,
+                           float *tex_coords_out)
+{
+    const float *mtx = pic->mtx;
+    assert(mtx);
+
+#define MTX(ROW,COL) mtx[(COL)*2+(ROW)]
+    for (unsigned i = 0; i < coords_count; ++i)
+    {
+        /* Store the coordinates, in case the transform must be applied in
+         * place (i.e. with pic_coords == tex_coords_out) */
+        float x = pic_coords[0];
+        float y = pic_coords[1];
+        tex_coords_out[0] = MTX(0,0) * x + MTX(0,1) * y + MTX(0,2);
+        tex_coords_out[1] = MTX(1,0) * x + MTX(1,1) * y + MTX(1,2);
+        pic_coords += 2;
+        tex_coords_out += 2;
+    }
+}
+
+void
+vlc_gl_picture_ComputeDirectionMatrix(const struct vlc_gl_picture *pic,
+                                      float direction[static 2*2])
+{
+    /**
+     * The direction matrix is extracted from pic->mtx:
+     *
+     *    mtx = / a b c \
+     *          \ d e f /
+     *
+     * The last column (the offset part of the affine transformation) is
+     * discarded, and the 2 remaining column vectors are normalized to remove
+     * any scaling:
+     *
+     *    direction = / a/unorm  b/vnorm \
+     *                \ d/unorm  e/vnorm /
+     *
+     * where unorm = norm( / a \ ) and vnorm = norm( / b \ ).
+     *                     \ d /                     \ e /
+     */
+
+    float ux = pic->mtx[0];
+    float uy = pic->mtx[1];
+    float vx = pic->mtx[2];
+    float vy = pic->mtx[3];
+
+    float unorm = sqrt(ux * ux + uy * uy);
+    float vnorm = sqrt(vx * vx + vy * vy);
+
+    direction[0] = ux / unorm;
+    direction[1] = uy / unorm;
+    direction[2] = vx / vnorm;
+    direction[3] = vy / vnorm;
+}


=====================================
modules/video_output/opengl/picture.h
=====================================
@@ -0,0 +1,146 @@
+/*****************************************************************************
+ * picture.h
+ *****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * 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_GL_PICTURE_H
+#define VLC_GL_PICTURE_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_es.h>
+#include <vlc_picture.h>
+#include "gl_common.h"
+
+/**
+ * Format of an OpenGL picture
+ */
+struct vlc_gl_format {
+    video_format_t fmt;
+
+    GLenum tex_target;
+
+    unsigned tex_count;
+    GLsizei tex_widths[PICTURE_PLANE_MAX];
+    GLsizei tex_heights[PICTURE_PLANE_MAX];
+
+    GLsizei visible_widths[PICTURE_PLANE_MAX];
+    GLsizei visible_heights[PICTURE_PLANE_MAX];
+};
+
+/**
+ * OpenGL picture
+ *
+ * It can only be properly used if its format, described by a vlc_gl_format, is
+ * known.
+ */
+struct vlc_gl_picture {
+    GLuint textures[PICTURE_PLANE_MAX];
+
+    /**
+     * Matrix to convert from 2D pictures coordinates to texture coordinates
+     *
+     * tex_coords =     mtx    × pic_coords
+     *
+     *  / tex_x \    / a b c \   / pic_x \
+     *  \ tex_y / =  \ d e f / × | pic_y |
+     *                           \   1   /
+     *
+     * It is stored in column-major order: [a, d, b, e, c, f].
+     */
+    float mtx[2*3];
+
+    /**
+     * Indicate if the transform to convert picture coordinates to textures
+     * coordinates have changed due to the last picture.
+     *
+     * The filters should check this flag on every draw() call, and update
+     * their coordinates if necessary.
+     *
+     * It is guaranteed to be true for the first picture.
+     */
+    bool mtx_has_changed;
+};
+
+/**
+ * Convert from picture coordinates to texture coordinates, which can be used to
+ * sample at the correct location.
+ *
+ * This is a equivalent to retrieve the matrix and multiply manually.
+ *
+ * The picture and texture coords may point to the same memory, in that case
+ * the transformation is applied in place (overwriting the picture coordinates
+ * by the texture coordinates).
+ *
+ * \param picture the OpenGL picture
+ * \param coords_count the number of coordinates (x,y) coordinates to convert
+ * \param pic_coords picture coordinates as an array of 2*coords_count floats
+ * \param tex_coords_out texture coordinates as an array of 2*coords_count
+ *                       floats
+ */
+void
+vlc_gl_picture_ToTexCoords(const struct vlc_gl_picture *pic,
+                           unsigned coords_count, const float *pic_coords,
+                           float *tex_coords_out);
+
+/**
+ * Return a matrix to orient texture coordinates
+ *
+ * This matrix is 2x2 and is stored in column-major order.
+ *
+ * While pic_to_tex_matrix transforms any picture coordinates into texture
+ * coordinates, it may be useful for example for vertex or fragment shaders to
+ * sample one pixel to the left of the current one, or two pixels to the top.
+ * Since the input texture may be rotated or flipped, the shaders need to
+ * know in which direction is the top and in which direction is the right of
+ * the picture.
+ *
+ * This 2x2 matrix allows to transform a 2D vector expressed in picture
+ * coordinates into a 2D vector expressed in texture coordinates.
+ *
+ * Concretely, it contains the coordinates (U, V) of the transformed unit
+ * vectors u = / 1 \ and v = / 0 \:
+ *             \ 0 /         \ 1 /
+ *
+ *     / Ux Vx \
+ *     \ Uy Vy /
+ *
+ * It is guaranteed that:
+ *  - both U and V are unit vectors (this matrix does not change the scaling);
+ *  - only one of their components have a non-zero value (they may not be
+ *    oblique); in other words, here are the possible values for U and V:
+ *
+ *        /  0 \  or  / 0 \  or  / 1 \  or  / -1 \
+ *        \ -1 /      \ 1 /      \ 0 /      \  0 /
+ *
+ *  - U and V are orthogonal.
+ *
+ * Therefore, there are 8 possible matrices (4 possible rotations, flipped or
+ * not).
+ *
+ * It may theoretically change on every picture (the transform matrix provided
+ * by Android may change). If it has changed since the last picture, then
+ * pic->mtx_has_changed is true.
+ */
+void
+vlc_gl_picture_ComputeDirectionMatrix(const struct vlc_gl_picture *pic,
+                                      float direction[static 2*2]);
+
+#endif


=====================================
modules/video_output/opengl/renderer.c
=====================================
@@ -40,6 +40,7 @@
 #include "filter.h"
 #include "gl_util.h"
 #include "vout_helper.h"
+#include "sampler.h"
 
 #define SPHERE_RADIUS 1.f
 
@@ -226,15 +227,15 @@ opengl_link_program(struct vlc_gl_filter *filter)
 
     if (renderer->dump_shaders)
     {
+        video_format_t *fmt = &sampler->glfmt.fmt;
         msg_Dbg(filter, "\n=== Vertex shader for fourcc: %4.4s ===\n",
-                (const char *) &renderer->sampler->fmt.i_chroma);
+                (const char *) &fmt->i_chroma);
         for (unsigned i = 0; i < ARRAY_SIZE(vertex_shader); ++i)
             msg_Dbg(filter, "[%u] %s", i, vertex_shader[i]);
 
         msg_Dbg(filter,
                 "\n=== Fragment shader for fourcc: %4.4s, colorspace: %d ===\n",
-                (const char *) &renderer->sampler->fmt.i_chroma,
-                sampler->fmt.space);
+                (const char *) &fmt->i_chroma, fmt->space);
         for (unsigned i = 0; i < ARRAY_SIZE(fragment_shader); ++i)
             msg_Dbg(filter, "[%u] %s", i, fragment_shader[i]);
     }
@@ -290,6 +291,8 @@ Close(struct vlc_gl_filter *filter)
     struct vlc_gl_renderer *renderer = filter->sys;
     const opengl_vtable_t *vt = renderer->vt;
 
+    vlc_gl_sampler_Delete(renderer->sampler);
+
     vt->DeleteBuffers(1, &renderer->vertex_buffer_object);
     vt->DeleteBuffers(1, &renderer->index_buffer_object);
     vt->DeleteBuffers(1, &renderer->texture_buffer_object);
@@ -300,14 +303,17 @@ Close(struct vlc_gl_filter *filter)
     free(renderer);
 }
 
-static int SetupCoords(struct vlc_gl_renderer *renderer);
+static int SetupCoords(struct vlc_gl_renderer *renderer,
+                       const struct vlc_gl_picture *pic);
 
 static int
-Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta);
+Draw(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+     const struct vlc_gl_input_meta *meta);
 
 int
 vlc_gl_renderer_Open(struct vlc_gl_filter *filter,
                      const config_chain_t *config,
+                     const struct vlc_gl_format *glfmt,
                      struct vlc_gl_tex_size *size_out)
 {
     (void) config;
@@ -315,13 +321,17 @@ vlc_gl_renderer_Open(struct vlc_gl_filter *filter,
 
     const opengl_vtable_t *vt = &filter->api->vt;
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
+    struct vlc_gl_sampler *sampler =
+        vlc_gl_sampler_New(filter->gl, filter->api, glfmt, false);
     if (!sampler)
         return VLC_EGENERIC;
 
     struct vlc_gl_renderer *renderer = calloc(1, sizeof(*renderer));
     if (!renderer)
+    {
+        vlc_gl_sampler_Delete(sampler);
         return VLC_EGENERIC;
+    }
 
     static const struct vlc_gl_filter_ops filter_ops = {
         .draw = Draw,
@@ -343,7 +353,7 @@ vlc_gl_renderer_Open(struct vlc_gl_filter *filter,
         return ret;
     }
 
-    const video_format_t *fmt = &sampler->fmt;
+    const video_format_t *fmt = &sampler->glfmt.fmt;
     InitStereoMatrix(renderer->var.StereoMatrix, fmt->multiview_mode);
 
     getViewpointMatrixes(renderer, fmt->projection_mode);
@@ -408,7 +418,7 @@ vlc_gl_renderer_SetViewpoint(struct vlc_gl_renderer *renderer,
         UpdateFOVy(renderer);
         UpdateZ(renderer);
     }
-    const video_format_t *fmt = &renderer->sampler->fmt;
+    const video_format_t *fmt = &renderer->sampler->glfmt.fmt;
     getViewpointMatrixes(renderer, fmt->projection_mode);
 
     return VLC_SUCCESS;
@@ -425,7 +435,7 @@ vlc_gl_renderer_SetWindowAspectRatio(struct vlc_gl_renderer *renderer,
     UpdateFOVy(renderer);
     UpdateZ(renderer);
 
-    const video_format_t *fmt = &renderer->sampler->fmt;
+    const video_format_t *fmt = &renderer->sampler->glfmt.fmt;
     getViewpointMatrixes(renderer, fmt->projection_mode);
 }
 
@@ -675,11 +685,12 @@ static int BuildRectangle(GLfloat **vertexCoord, GLfloat **textureCoord, unsigne
     return VLC_SUCCESS;
 }
 
-static int SetupCoords(struct vlc_gl_renderer *renderer)
+static int SetupCoords(struct vlc_gl_renderer *renderer,
+                       const struct vlc_gl_picture *pic)
 {
     const opengl_vtable_t *vt = renderer->vt;
     struct vlc_gl_sampler *sampler = renderer->sampler;
-    const video_format_t *fmt = &sampler->fmt;
+    const video_format_t *fmt = &sampler->glfmt.fmt;
 
     GLfloat *vertexCoord, *textureCoord;
     GLushort *indices;
@@ -711,8 +722,7 @@ static int SetupCoords(struct vlc_gl_renderer *renderer)
         return i_ret;
 
     /* Transform picture-to-texture coordinates in place */
-    vlc_gl_sampler_PicToTexCoords(sampler, nbVertices, textureCoord,
-                                  textureCoord);
+    vlc_gl_picture_ToTexCoords(pic, nbVertices, textureCoord, textureCoord);
 
     vt->BindBuffer(GL_ARRAY_BUFFER, renderer->texture_buffer_object);
     vt->BufferData(GL_ARRAY_BUFFER, nbVertices * 2 * sizeof(GLfloat),
@@ -736,7 +746,8 @@ static int SetupCoords(struct vlc_gl_renderer *renderer)
 }
 
 static int
-Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
+Draw(struct vlc_gl_filter *filter, const struct vlc_gl_picture *pic,
+     const struct vlc_gl_input_meta *meta)
 {
     (void) meta;
 
@@ -748,15 +759,16 @@ Draw(struct vlc_gl_filter *filter, const struct vlc_gl_input_meta *meta)
 
     vt->UseProgram(renderer->program_id);
 
-    struct vlc_gl_sampler *sampler = vlc_gl_filter_GetSampler(filter);
+    struct vlc_gl_sampler *sampler = renderer->sampler;
+    vlc_gl_sampler_Update(sampler, pic);
     vlc_gl_sampler_Load(sampler);
 
-    if (vlc_gl_sampler_MustRecomputeCoords(sampler))
+    if (pic->mtx_has_changed)
         renderer->valid_coords = false;
 
     if (!renderer->valid_coords)
     {
-        int ret = SetupCoords(renderer);
+        int ret = SetupCoords(renderer, pic);
         if (ret != VLC_SUCCESS)
             return ret;
 


=====================================
modules/video_output/opengl/sampler.c
=====================================
@@ -22,7 +22,7 @@
 # include "config.h"
 #endif
 
-#include "sampler_priv.h"
+#include "sampler.h"
 
 #include <vlc_common.h>
 #include <vlc_memstream.h>
@@ -37,7 +37,6 @@
 #include "gl_api.h"
 #include "gl_common.h"
 #include "gl_util.h"
-#include "interop.h"
 
 struct vlc_gl_sampler_priv {
     struct vlc_gl_sampler sampler;
@@ -46,6 +45,8 @@ struct vlc_gl_sampler_priv {
     const struct vlc_gl_api *api;
     const opengl_vtable_t *vt; /* for convenience, same as &api->vt */
 
+    struct vlc_gl_picture pic;
+
     struct {
         GLint Textures[PICTURE_PLANE_MAX];
         GLint TexSizes[PICTURE_PLANE_MAX]; /* for GL_TEXTURE_RECTANGLE */
@@ -61,66 +62,10 @@ struct vlc_gl_sampler_priv {
     struct pl_shader *pl_sh;
     const struct pl_shader_res *pl_sh_res;
 
-    GLsizei tex_widths[PICTURE_PLANE_MAX];
-    GLsizei tex_heights[PICTURE_PLANE_MAX];
-
-    GLsizei visible_widths[PICTURE_PLANE_MAX];
-    GLsizei visible_heights[PICTURE_PLANE_MAX];
-
-    GLuint textures[PICTURE_PLANE_MAX];
-
-    GLenum tex_target;
-
-    struct {
-        unsigned int i_x_offset;
-        unsigned int i_y_offset;
-        unsigned int i_visible_width;
-        unsigned int i_visible_height;
-    } last_source;
-
-    /* A sampler supports 2 kinds of input.
-     *  - created with _NewFromInterop(), it receives input pictures from VLC
-     *    (picture_t) via _UpdatePicture();
-     *  - created with _NewFromTexture2D() (interop is NULL), it receives
-     *    directly OpenGL textures via _UpdateTextures().
-     */
-    struct vlc_gl_interop *interop;
-
-    /* Only used for "direct" sampler (when interop == NULL) */
-    video_format_t direct_fmt;
-
     /* If set, vlc_texture() exposes a single plane (without chroma
      * conversion), selected by vlc_gl_sampler_SetCurrentPlane(). */
     bool expose_planes;
     unsigned plane;
-
-    /* All matrices below are stored in column-major order. */
-
-    float mtx_orientation[2*3];
-    float mtx_coords_map[2*3];
-
-    float mtx_transform[2*3];
-    bool mtx_transform_defined;
-
-    /**
-     * tex_coords =   mtx_all  × pic_coords
-     *
-     *  / tex_x \    / a b c \   / pic_x \
-     *  \ tex_y / =  \ d e f / × | pic_y |
-     *                           \   1   /
-     *
-     * Semantically, it represents the result of:
-     *
-     *     get_transform_matrix() * mtx_coords_map * mtx_orientation
-     *
-     * (The intermediate matrices are implicitly expanded to 3x3 with [0 0 1]
-     * as the last row.)
-     *
-     * It is stored in column-major order: [a, d, b, e, c, f].
-     */
-    float mtx_all[2*3];
-    bool mtx_all_defined;
-    bool mtx_all_has_changed; /* since the previous picture */
 };
 
 static inline struct vlc_gl_sampler_priv *
@@ -308,8 +253,10 @@ sampler_base_fetch_locations(struct vlc_gl_sampler *sampler, GLuint program)
         assert(priv->uloc.ConvMatrix != -1);
     }
 
-    assert(sampler->tex_count < 10); /* to guarantee variable names length */
-    for (unsigned int i = 0; i < sampler->tex_count; ++i)
+    struct vlc_gl_format *glfmt = &sampler->glfmt;
+
+    assert(glfmt->tex_count < 10); /* to guarantee variable names length */
+    for (unsigned int i = 0; i < glfmt->tex_count; ++i)
     {
         char name[sizeof("Textures[X]")];
 
@@ -317,7 +264,7 @@ sampler_base_fetch_locations(struct vlc_gl_sampler *sampler, GLuint program)
         priv->uloc.Textures[i] = vt->GetUniformLocation(program, name);
         assert(priv->uloc.Textures[i] != -1);
 
-        if (priv->tex_target == GL_TEXTURE_RECTANGLE)
+        if (glfmt->tex_target == GL_TEXTURE_RECTANGLE)
         {
             snprintf(name, sizeof(name), "TexSizes[%1u]", i);
             priv->uloc.TexSizes[i] = vt->GetUniformLocation(program, name);
@@ -334,41 +281,34 @@ sampler_base_fetch_locations(struct vlc_gl_sampler *sampler, GLuint program)
 #endif
 }
 
-static const GLfloat *
-GetTransformMatrix(const struct vlc_gl_interop *interop)
-{
-    const GLfloat *tm = NULL;
-    if (interop && interop->ops && interop->ops->get_transform_matrix)
-        tm = interop->ops->get_transform_matrix(interop);
-    return tm;
-}
-
 static void
 sampler_base_load(struct vlc_gl_sampler *sampler)
 {
     struct vlc_gl_sampler_priv *priv = PRIV(sampler);
 
     const opengl_vtable_t *vt = priv->vt;
+    struct vlc_gl_format *glfmt = &sampler->glfmt;
+    struct vlc_gl_picture *pic = &priv->pic;
 
     if (priv->yuv_color)
         vt->UniformMatrix4fv(priv->uloc.ConvMatrix, 1, GL_FALSE,
                              priv->conv_matrix);
 
-    for (unsigned i = 0; i < sampler->tex_count; ++i)
+    for (unsigned i = 0; i < glfmt->tex_count; ++i)
     {
         vt->Uniform1i(priv->uloc.Textures[i], i);
 
-        assert(priv->textures[i] != 0);
+        assert(pic->textures[i] != 0);
         vt->ActiveTexture(GL_TEXTURE0 + i);
-        vt->BindTexture(priv->tex_target, priv->textures[i]);
+        vt->BindTexture(glfmt->tex_target, pic->textures[i]);
 
     }
 
-    if (priv->tex_target == GL_TEXTURE_RECTANGLE)
+    if (glfmt->tex_target == GL_TEXTURE_RECTANGLE)
     {
-        for (unsigned i = 0; i < sampler->tex_count; ++i)
-            vt->Uniform2f(priv->uloc.TexSizes[i], priv->tex_widths[i],
-                          priv->tex_heights[i]);
+        for (unsigned i = 0; i < glfmt->tex_count; ++i)
+            vt->Uniform2f(priv->uloc.TexSizes[i], glfmt->tex_widths[i],
+                          glfmt->tex_heights[i]);
     }
 
 #ifdef HAVE_LIBPLACEBO
@@ -420,12 +360,14 @@ sampler_xyz12_load(struct vlc_gl_sampler *sampler)
 {
     struct vlc_gl_sampler_priv *priv = PRIV(sampler);
     const opengl_vtable_t *vt = priv->vt;
+    struct vlc_gl_format *glfmt = &sampler->glfmt;
+    struct vlc_gl_picture *pic = &priv->pic;
 
     vt->Uniform1i(priv->uloc.Textures[0], 0);
 
-    assert(priv->textures[0] != 0);
+    assert(pic->textures[0] != 0);
     vt->ActiveTexture(GL_TEXTURE0);
-    vt->BindTexture(priv->tex_target, priv->textures[0]);
+    vt->BindTexture(glfmt->tex_target, pic->textures[0]);
 }
 
 static int
@@ -534,155 +476,6 @@ opengl_init_swizzle(struct vlc_gl_sampler *sampler,
     return VLC_SUCCESS;
 }
 
-static void
-InitOrientationMatrix(float matrix[static 2*3], video_orientation_t orientation)
-{
-/**
- * / C0R0  C1R0  C3R0 \
- * \ C0R1  C1R1  C3R1 /
- *
- * (note that in memory, the matrix is stored in column-major order)
- */
-#define MATRIX_SET(C0R0, C1R0, C3R0, \
-                   C0R1, C1R1, C3R1) \
-    matrix[0*2 + 0] = C0R0; \
-    matrix[1*2 + 0] = C1R0; \
-    matrix[2*2 + 0] = C3R0; \
-    matrix[0*2 + 1] = C0R1; \
-    matrix[1*2 + 1] = C1R1; \
-    matrix[2*2 + 1] = C3R1;
-
-    /**
-     * The following schemas show how the video picture is oriented in the
-     * texture, according to the "orientation" value:
-     *
-     *     video         texture
-     *    picture        storage
-     *
-     *     1---2          2---3
-     *     |   |   --->   |   |
-     *     4---3          1---4
-     *
-     * In addition, they show how the orientation transforms video picture
-     * coordinates axis (x,y) into texture axis (X,Y):
-     *
-     *   y         --->         X
-     *   |                      |
-     *   +---x              Y---+
-     *
-     * The resulting coordinates undergo the reverse of the transformation
-     * applied to the axis, so expressing (x,y) in terms of (X,Y) gives the
-     * orientation matrix coefficients.
-     */
-
-    switch (orientation) {
-        case ORIENT_ROTATED_90:
-            /**
-             *     1---2          2---3
-             *   y |   |   --->   |   | X
-             *   | 4---3          1---4 |
-             *   +---x              Y---+
-             *
-             *          x = 1-Y
-             *          y = X
-             */
-                     /* X  Y  1 */
-            MATRIX_SET( 0,-1, 1, /* 1-Y */
-                        1, 0, 0) /* X */
-            break;
-        case ORIENT_ROTATED_180:
-            /**
-             *                      X---+
-             *     1---2          3---4 |
-             *   y |   |   --->   |   | Y
-             *   | 4---3          2---1
-             *   +---x
-             *
-             *          x = 1-X
-             *          y = 1-Y
-             */
-                     /* X  Y  1 */
-            MATRIX_SET(-1, 0, 1, /* 1-X */
-                        0,-1, 1) /* 1-Y */
-            break;
-        case ORIENT_ROTATED_270:
-            /**
-             *                    +---Y
-             *     1---2          | 4---1
-             *   y |   |   --->   X |   |
-             *   | 4---3            3---2
-             *   +---x
-             *
-             *          x = Y
-             *          y = 1-X
-             */
-                     /* X  Y  1 */
-            MATRIX_SET( 0, 1, 0, /* Y */
-                       -1, 0, 1) /* 1-X */
-            break;
-        case ORIENT_HFLIPPED:
-            /**
-             *     1---2          2---1
-             *   y |   |   --->   |   | Y
-             *   | 4---3          3---4 |
-             *   +---x              X---+
-             *
-             *          x = 1-X
-             *          y = Y
-             */
-                     /* X  Y  1 */
-            MATRIX_SET(-1, 0, 1, /* 1-X */
-                        0, 1, 0) /* Y */
-            break;
-        case ORIENT_VFLIPPED:
-            /**
-             *                    +---X
-             *     1---2          | 4---3
-             *   y |   |   --->   Y |   |
-             *   | 4---3            1---2
-             *   +---x
-             *
-             *          x = X
-             *          y = 1-Y
-             */
-                     /* X  Y  1 */
-            MATRIX_SET( 1, 0, 0, /* X */
-                        0,-1, 1) /* 1-Y */
-            break;
-        case ORIENT_TRANSPOSED:
-            /**
-             *                      Y---+
-             *     1---2          1---4 |
-             *   y |   |   --->   |   | X
-             *   | 4---3          2---3
-             *   +---x
-             *
-             *          x = 1-Y
-             *          y = 1-X
-             */
-                     /* X  Y  1 */
-            MATRIX_SET( 0,-1, 1, /* 1-Y */
-                       -1, 0, 1) /* 1-X */
-            break;
-        case ORIENT_ANTI_TRANSPOSED:
-            /**
-             *     1---2            3---2
-             *   y |   |   --->   X |   |
-             *   | 4---3          | 4---1
-             *   +---x            +---Y
-             *
-             *          x = Y
-             *          y = X
-             */
-                     /* X  Y  1 */
-            MATRIX_SET( 0, 1, 0, /* Y */
-                        1, 0, 0) /* X */
-            break;
-        default:
-            break;
-    }
-}
-
 static void
 GetNames(GLenum tex_target, const char **glsl_sampler, const char **texture)
 {
@@ -727,11 +520,12 @@ sampler_planes_fetch_locations(struct vlc_gl_sampler *sampler, GLuint program)
     struct vlc_gl_sampler_priv *priv = PRIV(sampler);
 
     const opengl_vtable_t *vt = priv->vt;
+    struct vlc_gl_format *glfmt = &sampler->glfmt;
 
     priv->uloc.Textures[0] = vt->GetUniformLocation(program, "Texture");
     assert(priv->uloc.Textures[0] != -1);
 
-    if (priv->tex_target == GL_TEXTURE_RECTANGLE)
+    if (glfmt->tex_target == GL_TEXTURE_RECTANGLE)
     {
         priv->uloc.TexSizes[0] = vt->GetUniformLocation(program, "TexSize");
         assert(priv->uloc.TexSizes[0] != -1);
@@ -745,25 +539,27 @@ sampler_planes_load(struct vlc_gl_sampler *sampler)
     unsigned plane = priv->plane;
 
     const opengl_vtable_t *vt = priv->vt;
+    struct vlc_gl_format *glfmt = &sampler->glfmt;
+    struct vlc_gl_picture *pic = &priv->pic;
 
     vt->Uniform1i(priv->uloc.Textures[0], 0);
 
-    assert(priv->textures[plane] != 0);
+    assert(pic->textures[plane] != 0);
     vt->ActiveTexture(GL_TEXTURE0);
-    vt->BindTexture(priv->tex_target, priv->textures[plane]);
+    vt->BindTexture(glfmt->tex_target, pic->textures[plane]);
 
-    if (priv->tex_target == GL_TEXTURE_RECTANGLE)
+    if (glfmt->tex_target == GL_TEXTURE_RECTANGLE)
     {
-        vt->Uniform2f(priv->uloc.TexSizes[0], priv->tex_widths[plane],
-                      priv->tex_heights[plane]);
+        vt->Uniform2f(priv->uloc.TexSizes[0], glfmt->tex_widths[plane],
+                      glfmt->tex_heights[plane]);
     }
 }
 
 static int
 sampler_planes_init(struct vlc_gl_sampler *sampler)
 {
-    struct vlc_gl_sampler_priv *priv = PRIV(sampler);
-    GLenum tex_target = priv->tex_target;
+    struct vlc_gl_format *glfmt = &sampler->glfmt;
+    GLenum tex_target = glfmt->tex_target;
 
     struct vlc_memstream ms;
     if (vlc_memstream_open(&ms))
@@ -816,18 +612,19 @@ sampler_planes_init(struct vlc_gl_sampler *sampler)
 }
 
 static int
-opengl_fragment_shader_init(struct vlc_gl_sampler *sampler, GLenum tex_target,
-                            const video_format_t *fmt, bool expose_planes)
+opengl_fragment_shader_init(struct vlc_gl_sampler *sampler, bool expose_planes)
 {
     struct vlc_gl_sampler_priv *priv = PRIV(sampler);
+    struct vlc_gl_format *glfmt = &sampler->glfmt;
+    const video_format_t *fmt = &glfmt->fmt;
+
+    GLenum tex_target = glfmt->tex_target;
 
-    priv->tex_target = tex_target;
     priv->expose_planes = expose_planes;
     priv->plane = 0;
 
     vlc_fourcc_t chroma = fmt->i_chroma;
     video_color_space_t yuv_space = fmt->space;
-    video_orientation_t orientation = fmt->orientation;
 
     const char *swizzle_per_tex[PICTURE_PLANE_MAX] = { NULL, };
     const bool is_yuv = vlc_fourcc_IsYUV(chroma);
@@ -838,9 +635,7 @@ opengl_fragment_shader_init(struct vlc_gl_sampler *sampler, GLenum tex_target,
         return VLC_EGENERIC;
 
     unsigned tex_count = desc->plane_count;
-    sampler->tex_count = tex_count;
-
-    InitOrientationMatrix(priv->mtx_orientation, orientation);
+    assert(tex_count == glfmt->tex_count);
 
     if (expose_planes)
         return sampler_planes_init(sampler);
@@ -1023,10 +818,9 @@ opengl_fragment_shader_init(struct vlc_gl_sampler *sampler, GLenum tex_target,
     return VLC_SUCCESS;
 }
 
-static struct vlc_gl_sampler *
-CreateSampler(struct vlc_gl_interop *interop, struct vlc_gl_t *gl,
-              const struct vlc_gl_api *api, const video_format_t *fmt,
-              unsigned tex_target, bool expose_planes)
+struct vlc_gl_sampler *
+vlc_gl_sampler_New(struct vlc_gl_t *gl, const struct vlc_gl_api *api,
+                   const struct vlc_gl_format *glfmt, bool expose_planes)
 {
     struct vlc_gl_sampler_priv *priv = calloc(1, sizeof(*priv));
     if (!priv)
@@ -1039,29 +833,25 @@ CreateSampler(struct vlc_gl_interop *interop, struct vlc_gl_t *gl,
     priv->pl_sh = NULL;
     priv->pl_sh_res = NULL;
 
-    priv->interop = interop;
     priv->gl = gl;
     priv->api = api;
     priv->vt = &api->vt;
 
-    priv->mtx_transform_defined = false;
-    sampler->pic_to_tex_matrix = NULL;
-    priv->mtx_all_defined = false;
-    priv->mtx_all_has_changed = false;
+    struct vlc_gl_picture *pic = &priv->pic;
+    memcpy(pic->mtx, MATRIX2x3_IDENTITY, sizeof(MATRIX2x3_IDENTITY));
+    priv->pic.mtx_has_changed = true;
+
+    sampler->pic_to_tex_matrix = pic->mtx;
 
     /* Formats with palette are not supported. This also allows to copy
      * video_format_t without possibility of failure. */
-    assert(!sampler->fmt.p_palette);
+    assert(!glfmt->fmt.p_palette);
 
-    sampler->fmt = *fmt;
+    sampler->glfmt = *glfmt;
 
     sampler->shader.extensions = NULL;
     sampler->shader.body = NULL;
 
-    /* Expose the texture sizes publicly */
-    sampler->tex_widths = priv->tex_widths;
-    sampler->tex_heights = priv->tex_heights;
-
 #ifdef HAVE_LIBPLACEBO
     // Create the main libplacebo context
     priv->pl_ctx = vlc_placebo_CreateContext(VLC_OBJECT(gl));
@@ -1079,84 +869,21 @@ CreateSampler(struct vlc_gl_interop *interop, struct vlc_gl_t *gl,
     }
 #endif
 
-    int ret = opengl_fragment_shader_init(sampler, tex_target, fmt,
-                                          expose_planes);
+    int ret = opengl_fragment_shader_init(sampler, expose_planes);
     if (ret != VLC_SUCCESS)
     {
         free(sampler);
         return NULL;
     }
 
-    unsigned tex_count = sampler->tex_count;
-    assert(!interop || interop->tex_count == tex_count);
-
-    /* This might be updated in UpdatePicture for non-direct samplers */
-    memcpy(&priv->mtx_coords_map, MATRIX2x3_IDENTITY,
-           sizeof(MATRIX2x3_IDENTITY));
-
-    if (interop)
-    {
-        /* Texture size */
-        for (unsigned j = 0; j < interop->tex_count; j++) {
-            const GLsizei w = interop->fmt_out.i_visible_width  * interop->texs[j].w.num
-                            / interop->texs[j].w.den;
-            const GLsizei h = interop->fmt_out.i_visible_height * interop->texs[j].h.num
-                            / interop->texs[j].h.den;
-            priv->visible_widths[j] = w;
-            priv->visible_heights[j] = h;
-            if (interop->api->supports_npot) {
-                priv->tex_widths[j]  = w;
-                priv->tex_heights[j] = h;
-            } else {
-                priv->tex_widths[j]  = vlc_align_pot(w);
-                priv->tex_heights[j] = vlc_align_pot(h);
-            }
-        }
-
-        if (!interop->handle_texs_gen)
-        {
-            ret = vlc_gl_interop_GenerateTextures(interop, priv->tex_widths,
-                                                  priv->tex_heights,
-                                                  priv->textures);
-            if (ret != VLC_SUCCESS)
-            {
-                free(sampler);
-                return NULL;
-            }
-        }
-    }
-
     return sampler;
 }
 
-struct vlc_gl_sampler *
-vlc_gl_sampler_NewFromInterop(struct vlc_gl_interop *interop,
-                              bool expose_planes)
-{
-    return CreateSampler(interop, interop->gl, interop->api, &interop->fmt_out,
-                         interop->tex_target, expose_planes);
-}
-
-struct vlc_gl_sampler *
-vlc_gl_sampler_NewFromTexture2D(struct vlc_gl_t *gl,
-                                const struct vlc_gl_api *api,
-                                const video_format_t *fmt, bool expose_planes)
-{
-    return CreateSampler(NULL, gl, api, fmt, GL_TEXTURE_2D, expose_planes);
-}
-
 void
 vlc_gl_sampler_Delete(struct vlc_gl_sampler *sampler)
 {
     struct vlc_gl_sampler_priv *priv = PRIV(sampler);
 
-    struct vlc_gl_interop *interop = priv->interop;
-    if (interop && !interop->handle_texs_gen)
-    {
-        const opengl_vtable_t *vt = interop->vt;
-        vt->DeleteTextures(interop->tex_count, priv->textures);
-    }
-
 #ifdef HAVE_LIBPLACEBO
     FREENULL(priv->uloc.pl_vars);
     if (priv->pl_ctx)
@@ -1169,183 +896,12 @@ vlc_gl_sampler_Delete(struct vlc_gl_sampler *sampler)
     free(priv);
 }
 
-/**
- * Compute out = a * b, as if the 2x3 matrices were expanded to 3x3 with
- *  [0 0 1] as the last row.
- */
-static void
-MatrixMultiply(float out[static 2*3],
-               const float a[static 2*3], const float b[static 2*3])
-{
-    /* All matrices are stored in column-major order. */
-    for (unsigned i = 0; i < 3; ++i)
-        for (unsigned j = 0; j < 2; ++j)
-            out[i*2+j] = a[0*2+j] * b[i*2+0]
-                       + a[1*2+j] * b[i*2+1];
-
-    /* Multiply the last implicit row [0 0 1] of b, expanded to 3x3 */
-    out[2*2+0] += a[2*2+0];
-    out[2*2+1] += a[2*2+1];
-}
-
-static void
-UpdateMatrixAll(struct vlc_gl_sampler_priv *priv)
-{
-    float tmp[2*3];
-
-    float *out = priv->mtx_transform_defined ? tmp : priv->mtx_all;
-    /* out = mtx_coords_map * mtx_orientation */
-    MatrixMultiply(out, priv->mtx_coords_map, priv->mtx_orientation);
-
-    if (priv->mtx_transform_defined)
-        /* mtx_all = mtx_transform * tmp */
-        MatrixMultiply(priv->mtx_all, priv->mtx_transform, tmp);
-}
-
-int
-vlc_gl_sampler_UpdatePicture(struct vlc_gl_sampler *sampler, picture_t *picture)
-{
-    struct vlc_gl_sampler_priv *priv = PRIV(sampler);
-
-    const struct vlc_gl_interop *interop = priv->interop;
-    assert(interop);
-
-    const video_format_t *source = &picture->format;
-
-    bool mtx_changed = false;
-
-    if (!priv->mtx_all_defined
-     || source->i_x_offset != priv->last_source.i_x_offset
-     || source->i_y_offset != priv->last_source.i_y_offset
-     || source->i_visible_width != priv->last_source.i_visible_width
-     || source->i_visible_height != priv->last_source.i_visible_height)
-    {
-        memset(priv->mtx_coords_map, 0, sizeof(priv->mtx_coords_map));
-
-        /* The transformation is the same for all planes, even with power-of-two
-         * textures. */
-        float scale_w = priv->tex_widths[0];
-        float scale_h = priv->tex_heights[0];
-
-        /* 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.
-        */
-        float left   = (source->i_x_offset +                       0 ) / scale_w;
-        float top    = (source->i_y_offset +                       0 ) / scale_h;
-        float right  = (source->i_x_offset + source->i_visible_width ) / scale_w;
-        float bottom = (source->i_y_offset + source->i_visible_height) / scale_h;
-
-        /**
-         * This matrix converts from picture coordinates (in range [0; 1])
-         * to textures coordinates where the picture is actually stored
-         * (removing paddings).
-         *
-         *        texture           (in texture coordinates)
-         *       +----------------+--- 0.0
-         *       |                |
-         *       |  +---------+---|--- top
-         *       |  | picture |   |
-         *       |  +---------+---|--- bottom
-         *       |  .         .   |
-         *       |  .         .   |
-         *       +----------------+--- 1.0
-         *       |  .         .   |
-         *      0.0 left  right  1.0  (in texture coordinates)
-         *
-         * In particular:
-         *  - (0.0, 0.0) is mapped to (left, top)
-         *  - (1.0, 1.0) is mapped to (right, bottom)
-         *
-         * This is an affine 2D transformation, so the input coordinates
-         * are given as a 3D vector in the form (x, y, 1), and the output
-         * is (x', y').
-         *
-         * The paddings are l (left), r (right), t (top) and b (bottom).
-         *
-         *      matrix = / (r-l)   0     l \
-         *               \   0   (b-t)   t /
-         *
-         * It is stored in column-major order.
-         */
-        float *matrix = priv->mtx_coords_map;
-#define COL(x) (x*2)
-#define ROW(x) (x)
-        matrix[COL(0) + ROW(0)] = right - left;
-        matrix[COL(1) + ROW(1)] = bottom - top;
-        matrix[COL(2) + ROW(0)] = left;
-        matrix[COL(2) + ROW(1)] = top;
-#undef COL
-#undef ROW
-
-        mtx_changed = true;
-
-        priv->last_source.i_x_offset = source->i_x_offset;
-        priv->last_source.i_y_offset = source->i_y_offset;
-        priv->last_source.i_visible_width = source->i_visible_width;
-        priv->last_source.i_visible_height = source->i_visible_height;
-    }
-
-    /* Update the texture */
-    int ret = interop->ops->update_textures(interop, priv->textures,
-                                            priv->visible_widths,
-                                            priv->visible_heights, picture,
-                                            NULL);
-
-    const float *tm = GetTransformMatrix(interop);
-    if (tm) {
-        memcpy(priv->mtx_transform, tm, sizeof(priv->mtx_transform));
-        priv->mtx_transform_defined = true;
-        mtx_changed = true;
-    }
-    else if (priv->mtx_transform_defined)
-    {
-        priv->mtx_transform_defined = false;
-        mtx_changed = true;
-    }
-
-    if (!priv->mtx_all_defined || mtx_changed)
-    {
-        UpdateMatrixAll(priv);
-        priv->mtx_all_defined = true;
-        sampler->pic_to_tex_matrix = priv->mtx_all;
-        priv->mtx_all_has_changed = true;
-    }
-    else
-        priv->mtx_all_has_changed = false;
-
-    return ret;
-}
-
 int
-vlc_gl_sampler_UpdateTextures(struct vlc_gl_sampler *sampler, GLuint textures[],
-                              GLsizei tex_widths[], GLsizei tex_heights[])
+vlc_gl_sampler_Update(struct vlc_gl_sampler *sampler,
+                      const struct vlc_gl_picture *picture)
 {
     struct vlc_gl_sampler_priv *priv = PRIV(sampler);
-    assert(!priv->interop);
-
-    if (!priv->mtx_all_defined)
-    {
-        memcpy(priv->mtx_all, MATRIX2x3_IDENTITY, sizeof(MATRIX2x3_IDENTITY));
-        priv->mtx_all_defined = true;
-        priv->mtx_all_has_changed = true;
-
-        sampler->pic_to_tex_matrix = priv->mtx_all;
-    }
-    else
-        priv->mtx_all_has_changed = false;
-
-    unsigned tex_count = sampler->tex_count;
-    memcpy(priv->textures, textures, tex_count * sizeof(textures[0]));
-    memcpy(priv->tex_widths, tex_widths, tex_count * sizeof(tex_widths[0]));
-    memcpy(priv->tex_heights, tex_heights, tex_count * sizeof(tex_heights[0]));
+    priv->pic = *picture;
 
     return VLC_SUCCESS;
 }
@@ -1356,69 +912,3 @@ vlc_gl_sampler_SelectPlane(struct vlc_gl_sampler *sampler, unsigned plane)
     struct vlc_gl_sampler_priv *priv = PRIV(sampler);
     priv->plane = plane;
 }
-
-void
-vlc_gl_sampler_PicToTexCoords(struct vlc_gl_sampler *sampler,
-                              unsigned coords_count, const float *pic_coords,
-                              float *tex_coords_out)
-{
-    struct vlc_gl_sampler_priv *priv = PRIV(sampler);
-    const float *mtx = priv->mtx_all;
-#define MTX(ROW,COL) mtx[(COL)*2+(ROW)]
-    for (unsigned i = 0; i < coords_count; ++i)
-    {
-        /* Store the coordinates, in case the transform must be applied in
-         * place (i.e. with pic_coords == tex_coords_out) */
-        float x = pic_coords[0];
-        float y = pic_coords[1];
-        tex_coords_out[0] = MTX(0,0) * x + MTX(0,1) * y + MTX(0,2);
-        tex_coords_out[1] = MTX(1,0) * x + MTX(1,1) * y + MTX(1,2);
-        pic_coords += 2;
-        tex_coords_out += 2;
-    }
-}
-
-void
-vlc_gl_sampler_ComputeDirectionMatrix(struct vlc_gl_sampler *sampler,
-                                      float direction[static 2*2])
-{
-    /**
-     * The direction matrix is extracted from priv->mtx_all:
-     *
-     *    mtx_all = / a b c \
-     *              \ d e f /
-     *
-     * The last column (the offset part of the affine transformation) is
-     * discarded, and the 2 remaining column vectors are normalized to remove
-     * any scaling:
-     *
-     *    direction = / a/unorm  b/vnorm \
-     *                \ d/unorm  e/vnorm /
-     *
-     * where unorm = norm( / a \ ) and vnorm = norm( / b \ ).
-     *                     \ d /                     \ e /
-     */
-
-    struct vlc_gl_sampler_priv *priv = PRIV(sampler);
-    assert(priv->mtx_all_defined);
-
-    float ux = priv->mtx_all[0];
-    float uy = priv->mtx_all[1];
-    float vx = priv->mtx_all[2];
-    float vy = priv->mtx_all[3];
-
-    float unorm = sqrt(ux * ux + uy * uy);
-    float vnorm = sqrt(vx * vx + vy * vy);
-
-    direction[0] = ux / unorm;
-    direction[1] = uy / unorm;
-    direction[2] = vx / vnorm;
-    direction[3] = vy / vnorm;
-}
-
-bool
-vlc_gl_sampler_MustRecomputeCoords(struct vlc_gl_sampler *sampler)
-{
-    struct vlc_gl_sampler_priv *priv = PRIV(sampler);
-    return priv->mtx_all_has_changed;
-}


=====================================
modules/video_output/opengl/sampler.h
=====================================
@@ -29,7 +29,9 @@
 #include <vlc_opengl.h>
 #include <vlc_picture.h>
 
+#include "gl_api.h"
 #include "gl_common.h"
+#include "picture.h"
 
 /**
  * The purpose of a sampler is to provide pixel values of a VLC input picture,
@@ -51,14 +53,7 @@
  */
 struct vlc_gl_sampler {
     /* Input format */
-    video_format_t fmt;
-
-    /* Number of input planes */
-    unsigned tex_count;
-
-    /* Texture sizes (arrays of tex_count values) */
-    const GLsizei *tex_widths;
-    const GLsizei *tex_heights;
+    struct vlc_gl_format glfmt;
 
     /**
      * Matrix to convert from picture coordinates to texture coordinates
@@ -153,86 +148,46 @@ vlc_gl_sampler_Load(struct vlc_gl_sampler *sampler)
 }
 
 /**
- * Convert from picture coordinates to texture coordinates, which can be used to
- * sample at the correct location.
- *
- * This is a equivalent to retrieve the matrix and multiply manually.
+ * Create a new sampler
  *
- * The picture and texture coords may point to the same memory, in that case
- * the transformation is applied in place (overwriting the picture coordinates
- * by the texture coordinates).
+ * \param gl the OpenGL context
+ * \param api the OpenGL API
+ * \param glfmt the input format
+ * \param expose_planes if set, vlc_texture() exposes a single plane at a time
+ *                      (selected by vlc_gl_sampler_SetCurrentPlane())
+ */
+struct vlc_gl_sampler *
+vlc_gl_sampler_New(struct vlc_gl_t *gl, const struct vlc_gl_api *api,
+                   const struct vlc_gl_format *glfmt, bool expose_planes);
+
+/**
+ * Delete a sampler
  *
  * \param sampler the sampler
- * \param coords_count the number of coordinates (x,y) coordinates to convert
- * \param pic_coords picture coordinates as an array of 2*coords_count floats
- * \param tex_coords_out texture coordinates as an array of 2*coords_count
- *                       floats
  */
 void
-vlc_gl_sampler_PicToTexCoords(struct vlc_gl_sampler *sampler,
-                              unsigned coords_count, const float *pic_coords,
-                              float *tex_coords_out);
+vlc_gl_sampler_Delete(struct vlc_gl_sampler *sampler);
 
 /**
- * Return a matrix to orient texture coordinates
- *
- * This matrix is 2x2 and is stored in column-major order.
- *
- * While pic_to_tex_matrix transforms any picture coordinates into texture
- * coordinates, it may be useful for example for vertex or fragment shaders to
- * sample one pixel to the left of the current one, or two pixels to the top.
- * Since the input texture may be rotated or flipped, the shaders need to
- * know in which direction is the top and in which direction is the right of
- * the picture.
- *
- * This 2x2 matrix allows to transform a 2D vector expressed in picture
- * coordinates into a 2D vector expressed in texture coordinates.
- *
- * Concretely, it contains the coordinates (U, V) of the transformed unit
- * vectors u = / 1 \ and v = / 0 \:
- *             \ 0 /         \ 1 /
+ * Update the input textures
  *
- *     / Ux Vx \
- *     \ Uy Vy /
- *
- * It is guaranteed that:
- *  - both U and V are unit vectors (this matrix does not change the scaling);
- *  - only one of their components have a non-zero value (they may not be
- *    oblique); in other words, here are the possible values for U and V:
- *
- *        /  0 \  or  / 0 \  or  / 1 \  or  / -1 \
- *        \ -1 /      \ 1 /      \ 0 /      \  0 /
- *
- *  - U and V are orthogonal.
- *
- * Therefore, there are 8 possible matrices (4 possible rotations, flipped or
- * not).
- *
- * Calling this function before the first picture (i.e. when
- * sampler->pic_to_tex_matrix is NULL) results in undefined behavior.
- *
- * It may theoretically change on every picture (the transform matrix provided
- * by Android may change). If it has changed since the last picture, then
- * vlc_gl_sampler_MustRecomputeCoords() will return true.
+ * \param sampler the sampler
+ * \param picture the OpenGL picture
  */
-void
-vlc_gl_sampler_ComputeDirectionMatrix(struct vlc_gl_sampler *sampler,
-                                      float direction[static 2*2]);
+int
+vlc_gl_sampler_Update(struct vlc_gl_sampler *sampler,
+                      const struct vlc_gl_picture *picture);
 
 /**
- * Indicate if the transform to convert picture coordinates to textures
- * coordinates have changed due to the last picture.
+ * Select the plane to expose
  *
- * The filters should call this function on every draw() call, and update their
- * coordinates if necessary (using vlc_gl_sampler_PicToTexCoords()).
- *
- * It is guaranteed that it returns true for the first picture.
+ * If the sampler exposes planes separately (for plane filters), select the
+ * plane to expose via the GLSL function vlc_texture().
  *
  * \param sampler the sampler
- * \retval true if the transform has changed due to the last picture
- * \retval false if the transform remains the same
+ * \param plane the plane number
  */
-bool
-vlc_gl_sampler_MustRecomputeCoords(struct vlc_gl_sampler *sampler);
+void
+vlc_gl_sampler_SelectPlane(struct vlc_gl_sampler *sampler, unsigned plane);
 
 #endif


=====================================
modules/video_output/opengl/sampler_priv.h deleted
=====================================
@@ -1,110 +0,0 @@
-/*****************************************************************************
- * sampler_priv.h
- *****************************************************************************
- * Copyright (C) 2020 VLC authors and VideoLAN
- *
- * 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_GL_SAMPLER_PRIV_H
-#define VLC_GL_SAMPLER_PRIV_H
-
-#include <vlc_common.h>
-
-#include "gl_api.h"
-#include "sampler.h"
-
-struct vlc_gl_interop;
-
-/**
- * Create a new sampler from an interop
- *
- * It receives input pictures from `picture_t`, and uses the interop to
- * uploaded them to OpenGL textures.
- *
- * \param interop the interop
- * \param expose_planes if set, vlc_texture() exposes a single plane at a time
- *                      (selected by vlc_gl_sampler_SetCurrentPlane())
- */
-struct vlc_gl_sampler *
-vlc_gl_sampler_NewFromInterop(struct vlc_gl_interop *interop,
-                              bool expose_planes);
-
-/**
- * Create a new direct sampler
- *
- * It receives input textures directly (typically the output of a previous
- * filter), with target GL_TEXTURE_2D.
- *
- * \param gl the OpenGL context
- * \param api the OpenGL API
- * \param fmt the input format
- * \param expose_planes if set, vlc_texture() exposes a single plane at a time
- *                      (selected by vlc_gl_sampler_SetCurrentPlane())
- */
-struct vlc_gl_sampler *
-vlc_gl_sampler_NewFromTexture2D(struct vlc_gl_t *gl,
-                                const struct vlc_gl_api *api,
-                                const video_format_t *fmt, bool expose_planes);
-
-/**
- * Delete a sampler
- *
- * \param sampler the sampler
- */
-void
-vlc_gl_sampler_Delete(struct vlc_gl_sampler *sampler);
-
-/**
- * Update the input picture
- *
- * This changes the current input picture, available from the fragment shader.
- *
- * Warning: only call on sampler created by vlc_gl_sampler_NewFromInterop().
- *
- * \param sampler the sampler
- * \param picture the new picture
- */
-int
-vlc_gl_sampler_UpdatePicture(struct vlc_gl_sampler *sampler,
-                             picture_t *picture);
-
-/**
- * Update the input textures
- *
- * Warning: only call on sampler created by vlc_gl_sampler_NewFromTexture2D().
- *
- * \param sampler the sampler
- * \param textures the new textures, with target GL_TEXTURE_2D
- * \param tex_widths the textures width
- * \param tex_heights the textures height
- */
-int
-vlc_gl_sampler_UpdateTextures(struct vlc_gl_sampler *sampler, GLuint textures[],
-                              GLsizei tex_widths[], GLsizei tex_heights[]);
-
-/**
- * Select the plane to expose
- *
- * If the sampler exposes planes separately (for plane filters), select the
- * plane to expose via the GLSL function vlc_texture().
- *
- * \param sampler the sampler
- * \param plane the plane number
- */
-void
-vlc_gl_sampler_SelectPlane(struct vlc_gl_sampler *sampler, unsigned plane);
-
-#endif


=====================================
modules/video_output/opengl/vout_helper.c
=====================================
@@ -45,8 +45,6 @@
 #include "gl_util.h"
 #include "vout_helper.h"
 #include "renderer.h"
-#include "sampler.h"
-#include "sampler_priv.h"
 #include "sub_renderer.h"
 
 struct vout_display_opengl_t {



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/ea665a66852c73ac65b92d31864d9fd03fdb3905...c1770ffcee706f6f0cf4f9c93f8bdec5d22323fd

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/ea665a66852c73ac65b92d31864d9fd03fdb3905...c1770ffcee706f6f0cf4f9c93f8bdec5d22323fd
You're receiving this email because of your account on code.videolan.org.




More information about the vlc-commits mailing list