[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