[vlc-devel] [PATCH 20/27] opengl: enable multisampling

Romain Vimont rom1v at videolabs.io
Thu Jun 25 14:23:07 CEST 2020


Enable multisampling anti-aliasing:
 - render to an intermediate multisample renderbuffer;
 - resolve it to the output framebuffer.

Co-authored-by: Alexandre Janniaux <ajanni at videolabs.io>
---
 modules/video_output/opengl/filter.c      |  10 ++-
 modules/video_output/opengl/filter.h      |  12 +++
 modules/video_output/opengl/filter_priv.h |   4 +
 modules/video_output/opengl/filters.c     | 100 +++++++++++++++++++++-
 modules/video_output/opengl/gl_api.c      |  16 ++++
 modules/video_output/opengl/gl_api.h      |   3 +
 6 files changed, 141 insertions(+), 4 deletions(-)

diff --git a/modules/video_output/opengl/filter.c b/modules/video_output/opengl/filter.c
index 208ccc12cb..819f5078aa 100644
--- a/modules/video_output/opengl/filter.c
+++ b/modules/video_output/opengl/filter.c
@@ -50,6 +50,7 @@ vlc_gl_filter_New(vlc_object_t *parent, const struct vlc_gl_api *api)
     struct vlc_gl_filter *filter = &priv->filter;
     filter->api = api;
     filter->config.blend = false;
+    filter->config.msaa_level = 0;
     filter->ops = NULL;
     filter->sys = NULL;
     filter->module = NULL;
@@ -109,12 +110,19 @@ vlc_gl_filter_Delete(struct vlc_gl_filter *filter)
     if (priv->sampler)
         vlc_gl_sampler_Delete(priv->sampler);
 
+    const opengl_vtable_t *vt = &filter->api->vt;
+
     if (priv->has_framebuffer_out)
     {
-        const opengl_vtable_t *vt = &filter->api->vt;
         vt->DeleteFramebuffers(1, &priv->framebuffer_out);
         vt->DeleteTextures(1, &priv->texture_out);
     }
 
+    if (filter->config.msaa_level)
+    {
+        vt->DeleteFramebuffers(1, &priv->framebuffer_msaa);
+        vt->DeleteRenderbuffers(1, &priv->renderbuffer_msaa);
+    }
+
     vlc_object_delete(&filter->obj);
 }
diff --git a/modules/video_output/opengl/filter.h b/modules/video_output/opengl/filter.h
index 88b4f572b0..399087d68f 100644
--- a/modules/video_output/opengl/filter.h
+++ b/modules/video_output/opengl/filter.h
@@ -86,6 +86,18 @@ struct vlc_gl_filter {
          * This flag must be set by the filter module (default is false).
          */
         bool blend;
+
+        /**
+         * Request MSAA level.
+         *
+         * This value must be set by the filter module (default is 0, which
+         * means disabled).
+         *
+         * 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.
+         */
+        unsigned msaa_level;
     } config;
 
     const struct vlc_gl_filter_ops *ops;
diff --git a/modules/video_output/opengl/filter_priv.h b/modules/video_output/opengl/filter_priv.h
index de1c4cd319..f4c983ce85 100644
--- a/modules/video_output/opengl/filter_priv.h
+++ b/modules/video_output/opengl/filter_priv.h
@@ -41,6 +41,10 @@ struct vlc_gl_filter_priv {
     bool has_framebuffer_out;
     GLuint framebuffer_out; /* owned (this filter must delete it) */
     GLuint texture_out; /* owned (attached to framebuffer_out) */
+
+    /* For multisampling, if msaa_level != 0 */
+    GLuint framebuffer_msaa; /* owned */
+    GLuint renderbuffer_msaa; /* owned (attached to framebuffer_msaa) */
     /* } */
 
     /* For lazy-loading sampler */
diff --git a/modules/video_output/opengl/filters.c b/modules/video_output/opengl/filters.c
index 70710d69af..f22478893b 100644
--- a/modules/video_output/opengl/filters.c
+++ b/modules/video_output/opengl/filters.c
@@ -124,6 +124,34 @@ InitFramebufferOut(struct vlc_gl_filter_priv *priv)
     return VLC_SUCCESS;
 }
 
+static int
+InitFramebufferMSAA(struct vlc_gl_filter_priv *priv, unsigned msaa_level)
+{
+    assert(msaa_level);
+    assert(priv->size_out.width > 0 && priv->size_out.height > 0);
+
+    const opengl_vtable_t *vt = &priv->filter.api->vt;
+
+    vt->GenRenderbuffers(1, &priv->renderbuffer_msaa);
+    vt->BindRenderbuffer(GL_RENDERBUFFER, priv->renderbuffer_msaa);
+    vt->RenderbufferStorageMultisample(GL_RENDERBUFFER, msaa_level,
+                                       GL_RGBA8,
+                                       priv->size_out.width,
+                                       priv->size_out.height);
+
+    vt->GenFramebuffers(1, &priv->framebuffer_msaa);
+    vt->BindFramebuffer(GL_FRAMEBUFFER, priv->framebuffer_msaa);
+    vt->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                GL_RENDERBUFFER, priv->renderbuffer_msaa);
+
+    GLenum status = vt->CheckFramebufferStatus(GL_FRAMEBUFFER);
+    if (status != GL_FRAMEBUFFER_COMPLETE)
+        return VLC_EGENERIC;
+
+    vt->BindFramebuffer(GL_FRAMEBUFFER, 0);
+    return VLC_SUCCESS;
+}
+
 static struct vlc_gl_sampler *
 GetSampler(struct vlc_gl_filter *filter)
 {
@@ -256,9 +284,53 @@ vlc_gl_filters_Append(struct vlc_gl_filters *filters, const char *name,
 int
 vlc_gl_filters_InitFramebuffers(struct vlc_gl_filters *filters)
 {
-    struct vlc_gl_filter_priv *priv;
+    struct vlc_gl_filter_priv *priv = NULL;
+    struct vlc_gl_filter_priv *subfilter_priv;
+
     vlc_list_foreach(priv, &filters->list, node)
     {
+        /* Compute the highest msaa_level among the filter and its subfilters */
+        unsigned msaa_level = 0;
+        if (filters->api->supports_multisample)
+        {
+            msaa_level = priv->filter.config.msaa_level;
+            vlc_list_foreach(subfilter_priv, &priv->blend_subfilters, node)
+            {
+                if (subfilter_priv->filter.config.msaa_level > msaa_level)
+                    msaa_level = subfilter_priv->filter.config.msaa_level;
+            }
+        }
+
+        /* Update the actual msaa_level used to create the MSAA framebuffer */
+        priv->filter.config.msaa_level = msaa_level;
+        /* Also update the actual msaa_level for subfilters, just for info */
+        vlc_list_foreach(subfilter_priv, &priv->blend_subfilters, node)
+            subfilter_priv->filter.config.msaa_level = msaa_level;
+    }
+
+    /* "priv" is the last filter */
+    assert(priv); /* There is at least one filter */
+
+    if (priv->filter.config.msaa_level)
+    {
+        /* Resolving multisampling to the default framebuffer might fail,
+         * because its format may be different. So insert a "draw" filter. */
+        struct vlc_gl_filter *draw =
+            vlc_gl_filters_Append(filters, "draw", NULL);
+        if (!draw)
+            return VLC_EGENERIC;
+    }
+
+    vlc_list_foreach(priv, &filters->list, node)
+    {
+        unsigned msaa_level = priv->filter.config.msaa_level;
+        if (msaa_level)
+        {
+            int ret = InitFramebufferMSAA(priv, msaa_level);
+            if (ret != VLC_SUCCESS)
+                return ret;
+        }
+
         bool is_last = vlc_list_is_last(&priv->node, &filters->list);
         if (!is_last)
         {
@@ -326,8 +398,13 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
             }
         }
 
-        GLuint draw_fb = priv->has_framebuffer_out ? priv->framebuffer_out
-                                                   : draw_framebuffer;
+        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);
 
@@ -358,6 +435,23 @@ vlc_gl_filters_Draw(struct vlc_gl_filters *filters)
             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);
+        }
     }
 
     return VLC_SUCCESS;
diff --git a/modules/video_output/opengl/gl_api.c b/modules/video_output/opengl/gl_api.c
index 68c42cc6d4..46a27384fd 100644
--- a/modules/video_output/opengl/gl_api.c
+++ b/modules/video_output/opengl/gl_api.c
@@ -151,6 +151,22 @@ vlc_gl_api_Init(struct vlc_gl_api *api, vlc_gl_t *gl)
         return VLC_EGENERIC;
     }
 
+    const char *version = (const char *) api->vt.GetString(GL_VERSION);
+#ifdef USE_OPENGL_ES2
+#   define PREFIX "OpenGL ES "
+    /* starts with "OpenGL ES " */
+    assert(!strncmp(version, PREFIX, sizeof(PREFIX) - 1));
+    /* skip the prefix */
+    version += sizeof(PREFIX) - 1;
+#endif
+
+    /* OpenGL >= 3.0:
+     *     https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glRenderbufferStorageMultisample.xhtml
+     * OpenGL ES >= 3.0:
+     *     https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glRenderbufferStorageMultisample.xhtml
+     */
+    api->supports_multisample = strverscmp(version, "3.0") >= 0;
+
 #ifdef USE_OPENGL_ES2
     api->is_gles = true;
     /* OpenGL ES 2 includes support for non-power of 2 textures by specification
diff --git a/modules/video_output/opengl/gl_api.h b/modules/video_output/opengl/gl_api.h
index 2ddae33178..49b46ae110 100644
--- a/modules/video_output/opengl/gl_api.h
+++ b/modules/video_output/opengl/gl_api.h
@@ -43,6 +43,9 @@ struct vlc_gl_api {
 
     /* Non-power-of-2 texture size support */
     bool supports_npot;
+
+    /* Multisampling for anti-aliasing */
+    bool supports_multisample;
 };
 
 int
-- 
2.27.0



More information about the vlc-devel mailing list