[vlc-devel] [PATCH v2 12/13] opengl: add support for planes filtering

Romain Vimont rom1v at videolabs.io
Tue Mar 30 11:14:10 UTC 2021


If config.filter_planes is set on a filter, generate one output texture
for each input texture, and call draw() for each plane separately.
---
 modules/video_output/opengl/filter.c      |   8 +-
 modules/video_output/opengl/filter.h      |  18 ++
 modules/video_output/opengl/filter_priv.h |  12 +-
 modules/video_output/opengl/filters.c     | 235 +++++++++++++++-------
 4 files changed, 196 insertions(+), 77 deletions(-)

diff --git a/modules/video_output/opengl/filter.c b/modules/video_output/opengl/filter.c
index 819f5078aa..b7357c7b20 100644
--- a/modules/video_output/opengl/filter.c
+++ b/modules/video_output/opengl/filter.c
@@ -45,7 +45,7 @@ vlc_gl_filter_New(vlc_object_t *parent, const struct vlc_gl_api *api)
     priv->size_out.width = 0;
     priv->size_out.height = 0;
 
-    priv->has_framebuffer_out = false;
+    priv->tex_count = 0;
 
     struct vlc_gl_filter *filter = &priv->filter;
     filter->api = api;
@@ -112,10 +112,10 @@ vlc_gl_filter_Delete(struct vlc_gl_filter *filter)
 
     const opengl_vtable_t *vt = &filter->api->vt;
 
-    if (priv->has_framebuffer_out)
+    if (priv->tex_count)
     {
-        vt->DeleteFramebuffers(1, &priv->framebuffer_out);
-        vt->DeleteTextures(1, &priv->texture_out);
+        vt->DeleteFramebuffers(priv->tex_count, priv->framebuffers_out);
+        vt->DeleteTextures(priv->tex_count, priv->textures_out);
     }
 
     if (filter->config.msaa_level)
diff --git a/modules/video_output/opengl/filter.h b/modules/video_output/opengl/filter.h
index 399087d68f..f73887d081 100644
--- a/modules/video_output/opengl/filter.h
+++ b/modules/video_output/opengl/filter.h
@@ -35,6 +35,7 @@ struct vlc_gl_tex_size {
 
 struct vlc_gl_input_meta {
     vlc_tick_t pts;
+    unsigned plane;
 };
 
 typedef int
@@ -63,6 +64,9 @@ struct vlc_gl_filter_owner_ops {
      * 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
      */
@@ -80,9 +84,21 @@ struct vlc_gl_filter {
     const struct vlc_gl_api *api;
 
     struct {
+        /**
+         * An OpenGL filter may either operate on the input RGBA picture, or on
+         * individual input planes (without chroma conversion) separately.
+         *
+         * In practice, this is useful for deinterlace filters.
+         *
+         * This flag must be set by the filter module (default is false).
+         */
+        bool filter_planes;
+
         /**
          * A blend filter draws over the input picture (without reading it).
          *
+         * Meaningless if filter_planes is true.
+         *
          * This flag must be set by the filter module (default is false).
          */
         bool blend;
@@ -93,6 +109,8 @@ struct vlc_gl_filter {
          * This value must be set by the filter module (default is 0, which
          * means disabled).
          *
+         * Meaningless if filter_planes is true.
+         *
          * The actual MSAA level may be overwritten to 0 if multisampling is
          * not supported, or to a higher value if another filter rendering on
          * the same framebuffer requested a higher MSAA level.
diff --git a/modules/video_output/opengl/filter_priv.h b/modules/video_output/opengl/filter_priv.h
index f4c983ce85..e28bd99b61 100644
--- a/modules/video_output/opengl/filter_priv.h
+++ b/modules/video_output/opengl/filter_priv.h
@@ -24,6 +24,7 @@
 
 #include <vlc_common.h>
 #include <vlc_list.h>
+#include <vlc_picture.h>
 
 #include "filter.h"
 #include "sampler.h"
@@ -38,9 +39,14 @@ struct vlc_gl_filter_priv {
     /* Only meaningful for non-blend filters { */
     struct vlc_gl_sampler *sampler; /* owned */
 
-    bool has_framebuffer_out;
-    GLuint framebuffer_out; /* owned (this filter must delete it) */
-    GLuint texture_out; /* owned (attached to framebuffer_out) */
+    /* owned (this filter must delete it) */
+    GLuint framebuffers_out[PICTURE_PLANE_MAX];
+
+    /* owned (each attached to framebuffers_out[i]) */
+    GLuint textures_out[PICTURE_PLANE_MAX];
+    GLsizei tex_widths[PICTURE_PLANE_MAX];
+    GLsizei tex_heights[PICTURE_PLANE_MAX];
+    unsigned tex_count;
 
     /* For multisampling, if msaa_level != 0 */
     GLuint framebuffer_msaa; /* owned */
diff --git a/modules/video_output/opengl/filters.c b/modules/video_output/opengl/filters.c
index 7db8b5a685..c8e99a9029 100644
--- a/modules/video_output/opengl/filters.c
+++ b/modules/video_output/opengl/filters.c
@@ -170,17 +170,17 @@ vlc_gl_filters_Delete(struct vlc_gl_filters *filters)
 }
 
 static int
-InitFramebufferOut(struct vlc_gl_filter_priv *priv)
+InitPlane(struct vlc_gl_filter_priv *priv, unsigned plane, GLsizei width,
+          GLsizei height)
 {
-    assert(priv->size_out.width > 0 && priv->size_out.height > 0);
-
     const opengl_vtable_t *vt = &priv->filter.api->vt;
 
-    /* Create a texture having the expected size */
-    vt->GenTextures(1, &priv->texture_out);
-    vt->BindTexture(GL_TEXTURE_2D, priv->texture_out);
-    vt->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, priv->size_out.width,
-                   priv->size_out.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+    GLuint framebuffer = priv->framebuffers_out[plane];
+    GLuint texture = priv->textures_out[plane];
+
+    vt->BindTexture(GL_TEXTURE_2D, texture);
+    vt->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
+                   GL_UNSIGNED_BYTE, NULL);
     vt->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     vt->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
@@ -189,12 +189,9 @@ InitFramebufferOut(struct vlc_gl_filter_priv *priv)
     vt->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
     /* Create a framebuffer and attach the texture */
-    vt->GenFramebuffers(1, &priv->framebuffer_out);
-    vt->BindFramebuffer(GL_FRAMEBUFFER, priv->framebuffer_out);
+    vt->BindFramebuffer(GL_FRAMEBUFFER, framebuffer);
     vt->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
-                             GL_TEXTURE_2D, priv->texture_out, 0);
-
-    priv->has_framebuffer_out = true;
+                             GL_TEXTURE_2D, texture, 0);
 
     GLenum status = vt->CheckFramebufferStatus(GL_FRAMEBUFFER);
     if (status != GL_FRAMEBUFFER_COMPLETE)
@@ -203,6 +200,57 @@ InitFramebufferOut(struct vlc_gl_filter_priv *priv)
     return VLC_SUCCESS;
 }
 
+static int
+InitFramebuffersOut(struct vlc_gl_filter_priv *priv)
+{
+    assert(priv->size_out.width > 0 && priv->size_out.height > 0);
+
+    const opengl_vtable_t *vt = &priv->filter.api->vt;
+
+    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;
+
+        priv->tex_count = sampler->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)
+        {
+            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];
+            /* Init one framebuffer and texture for each plane */
+            int ret =
+                InitPlane(priv, i, priv->tex_widths[i], priv->tex_heights[i]);
+            if (ret != VLC_SUCCESS)
+                return ret;
+        }
+    }
+    else
+    {
+        priv->tex_count = 1;
+
+        /* Create a texture having the expected size */
+
+        vt->GenFramebuffers(1, priv->framebuffers_out);
+        vt->GenTextures(1, priv->textures_out);
+
+        priv->tex_widths[0] = priv->size_out.width;
+        priv->tex_heights[0] = priv->size_out.height;
+
+        int ret = InitPlane(priv, 0, priv->tex_widths[0], priv->tex_heights[0]);
+        if (ret != VLC_SUCCESS)
+            return ret;
+    }
+
+    return VLC_SUCCESS;
+}
+
 static int
 InitFramebufferMSAA(struct vlc_gl_filter_priv *priv, unsigned msaa_level)
 {
@@ -241,18 +289,27 @@ GetSampler(struct vlc_gl_filter *filter)
     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, false);
+        sampler = vlc_gl_sampler_NewFromInterop(filters->interop,
+                                                expose_planes);
     else
     {
         video_format_t fmt;
-        video_format_Init(&fmt, VLC_CODEC_RGBA);
+
+        /* 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, false);
+                                                  &fmt, expose_planes);
     }
 
     priv->sampler = sampler;
@@ -312,6 +369,12 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
            || (priv->size_out.width == size_in.width
             && priv->size_out.height == size_in.height));
 
+    /* A filter operating on planes may not blend. */
+    assert(!filter->config.filter_planes || !filter->config.blend);
+
+    /* 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.
      *
@@ -323,7 +386,7 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
 
     if (filter->config.blend)
     {
-        if (!prev_filter)
+        if (!prev_filter || prev_filter->filter.config.filter_planes)
         {
             /* We cannot blend with nothing, so insert a "draw" filter to draw
              * the input picture to blend with. */
@@ -398,10 +461,15 @@ vlc_gl_filters_InitFramebuffers(struct vlc_gl_filters *filters)
     /* "priv" is the last filter */
     assert(priv); /* There is at least one filter */
 
-    if (priv->filter.config.msaa_level)
-    {
+    bool insert_draw =
         /* Resolving multisampling to the default framebuffer might fail,
-         * because its format may be different. So insert a "draw" filter. */
+         * because its format may be different. */
+        priv->filter.config.msaa_level ||
+        /* A filter operating on planes may produce several textures.
+         * They need to be chroma-converted to a single RGBA texture. */
+        priv->filter.config.filter_planes;
+    if (insert_draw)
+    {
         struct vlc_gl_filter *draw =
             vlc_gl_filters_Append(filters, "draw", NULL);
         if (!draw)
@@ -422,11 +490,11 @@ vlc_gl_filters_InitFramebuffers(struct vlc_gl_filters *filters)
         if (!is_last)
         {
             /* It was the last non-blend filter before we append this one */
-            assert(!priv->has_framebuffer_out);
+            assert(priv->tex_count == 0);
 
             /* Every non-blend filter needs its own framebuffer, except the last
              * one */
-            int ret = InitFramebufferOut(priv);
+            int ret = InitFramebuffersOut(priv);
             if (ret != VLC_SUCCESS)
                 return ret;
         }
@@ -467,6 +535,7 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
 
     struct vlc_gl_input_meta meta = {
         .pts = filters->pic.pts,
+        .plane = 0,
     };
 
     struct vlc_gl_filter_priv *priv;
@@ -478,11 +547,10 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
         if (previous)
         {
             /* Read from the output of the previous filter */
-            GLuint tex = previous->texture_out;
-            GLsizei width = previous->size_out.width;
-            GLsizei height = previous->size_out.height;
-            int ret = vlc_gl_sampler_UpdateTextures(priv->sampler, &tex, &width,
-                                                    &height);
+            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");
@@ -490,59 +558,86 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
             }
         }
 
-        unsigned msaa_level = priv->filter.config.msaa_level;
-        GLuint draw_fb;
-        if (msaa_level)
-            draw_fb = priv->framebuffer_msaa;
-        else
-            draw_fb = priv->has_framebuffer_out ? priv->framebuffer_out
-                                                : draw_framebuffer;
-
-        vt->BindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_fb);
+        struct vlc_gl_filter *filter = &priv->filter;
 
-        if (vlc_list_is_last(&priv->node, &filters->list))
+        if (filter->config.filter_planes)
         {
-            /* The output viewport must be applied on the last filter */
-            struct vlc_gl_filters_viewport *vp = &filters->viewport;
-            vt->Viewport(vp->x, vp->y, vp->width, vp->height);
-        }
-        else
-            vt->Viewport(0, 0, priv->size_out.width, priv->size_out.height);
+            for (unsigned i = 0; i < priv->tex_count; ++i)
+            {
+                meta.plane = i;
 
-        struct vlc_gl_filter *filter = &priv->filter;
-        int ret = filter->ops->draw(filter, &meta);
-        if (ret != VLC_SUCCESS)
-            return ret;
+                /* Select the output texture associated to this plane */
+                GLuint draw_fb = priv->framebuffers_out[i];
+                vt->BindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_fb);
 
-        /* Draw blend subfilters */
-        struct vlc_gl_filter_priv *subfilter_priv;
-        vlc_list_foreach(subfilter_priv, &priv->blend_subfilters, node)
+                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);
+                if (ret != VLC_SUCCESS)
+                    return ret;
+            }
+        }
+        else
         {
-            /* Reset the draw buffer, in case it has been changed from a filter
-             * draw() callback */
+            assert(priv->tex_count <= 1);
+            unsigned msaa_level = priv->filter.config.msaa_level;
+            GLuint draw_fb;
+            if (msaa_level)
+                draw_fb = priv->framebuffer_msaa;
+            else
+                draw_fb = priv->tex_count > 0 ? priv->framebuffers_out[0]
+                                              : draw_framebuffer;
+
             vt->BindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_fb);
 
-            struct vlc_gl_filter *subfilter = &subfilter_priv->filter;
-            ret = subfilter->ops->draw(subfilter, &meta);
+            if (vlc_list_is_last(&priv->node, &filters->list))
+            {
+                /* The output viewport must be applied on the last filter */
+                struct vlc_gl_filters_viewport *vp = &filters->viewport;
+                vt->Viewport(vp->x, vp->y, vp->width, vp->height);
+            }
+            else
+                vt->Viewport(0, 0, priv->tex_widths[0], priv->tex_heights[0]);
+
+            meta.plane = 0;
+            int ret = filter->ops->draw(filter, &meta);
             if (ret != VLC_SUCCESS)
                 return ret;
-        }
 
-        if (filter->config.msaa_level)
-        {
-            /* Never resolve multisampling to the default framebuffer */
-            assert(priv->has_framebuffer_out);
-            assert(priv->framebuffer_out != draw_framebuffer);
-
-            /* Resolve the MSAA into the target framebuffer */
-            vt->BindFramebuffer(GL_READ_FRAMEBUFFER, priv->framebuffer_msaa);
-            vt->BindFramebuffer(GL_DRAW_FRAMEBUFFER, priv->framebuffer_out);
-
-            GLint width = priv->size_out.width;
-            GLint height = priv->size_out.height;
-            vt->BlitFramebuffer(0, 0, width, height,
-                                0, 0, width, height,
-                                GL_COLOR_BUFFER_BIT, GL_NEAREST);
+            /* Draw blend subfilters */
+            struct vlc_gl_filter_priv *subfilter_priv;
+            vlc_list_foreach(subfilter_priv, &priv->blend_subfilters, node)
+            {
+                /* Reset the draw buffer, in case it has been changed from a
+                 * filter draw() callback */
+                vt->BindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_fb);
+
+                struct vlc_gl_filter *subfilter = &subfilter_priv->filter;
+                ret = subfilter->ops->draw(subfilter, &meta);
+                if (ret != VLC_SUCCESS)
+                    return ret;
+            }
+
+            if (filter->config.msaa_level)
+            {
+                /* Never resolve multisampling to the default framebuffer */
+                assert(priv->tex_count == 1);
+                assert(priv->framebuffers_out[0] != draw_framebuffer);
+
+                /* Resolve the MSAA into the target framebuffer */
+                vt->BindFramebuffer(GL_READ_FRAMEBUFFER,
+                                    priv->framebuffer_msaa);
+                vt->BindFramebuffer(GL_DRAW_FRAMEBUFFER,
+                                    priv->framebuffers_out[0]);
+
+                GLint width = priv->size_out.width;
+                GLint height = priv->size_out.height;
+                vt->BlitFramebuffer(0, 0, width, height,
+                                    0, 0, width, height,
+                                    GL_COLOR_BUFFER_BIT, GL_NEAREST);
+            }
         }
     }
 
-- 
2.31.0



More information about the vlc-devel mailing list