[vlc-devel] [PATCH 5/5] opengl: WIP introduce libplacebo sampling filters

Louis Régnier louis.videolabs at gmail.com
Fri Aug 28 18:49:39 CEST 2020


This patch allows to use libplacebo's polar sampling filters
(ewa_jinc, ewa_lanczos, ewa_ginseng, ewa_hann, HaasnSoft (blurred EWA Hann))
in order to perform scaling.

It introduce a new opengl sampler who handle scaling.
---
 modules/video_output/opengl/sampler.c     | 466 ++++++++++++++++++++++
 modules/video_output/opengl/vout_helper.h |   3 +
 modules/video_output/placebo_utils.h      |  23 ++
 3 files changed, 492 insertions(+)

diff --git a/modules/video_output/opengl/sampler.c b/modules/video_output/opengl/sampler.c
index ace1d723e1..997be3030d 100644
--- a/modules/video_output/opengl/sampler.c
+++ b/modules/video_output/opengl/sampler.c
@@ -29,8 +29,10 @@
 #include <vlc_opengl.h>
 
 #ifdef HAVE_LIBPLACEBO
+#include <libplacebo/dummy.h>
 #include <libplacebo/shaders.h>
 #include <libplacebo/shaders/colorspace.h>
+#include <libplacebo/shaders/sampling.h>
 #include "../placebo_utils.h"
 #endif
 
@@ -65,6 +67,15 @@ struct vlc_gl_sampler_priv {
     GLfloat conv_matrix[4*4];
 
     /* libplacebo context */
+    struct {
+        GLuint lut_texture;
+        GLuint texture;
+        GLint *pl_vars;
+        GLint *pl_desc;
+        const struct pl_shader_res *sh;
+        const struct pl_shader_res *sh_color;
+    } upscale;
+    const struct pl_gpu *pl_gpu;
     struct pl_context *pl_ctx;
     struct pl_shader *pl_sh;
     const struct pl_shader_res *pl_sh_res;
@@ -84,6 +95,9 @@ struct vlc_gl_sampler_priv {
     struct vlc_gl_interop *interop;
 };
 
+static const int polar_filters[] = { 0, SCALE_EWA_JINC, SCALE_EWA_LANCZOS,
+SCALE_EWA_GINSENG, SCALE_HAASNSOFT, SCALE_EWA_HANN };
+
 #define PRIV(sampler) container_of(sampler, struct vlc_gl_sampler_priv, sampler)
 
 static const float MATRIX_COLOR_RANGE_LIMITED[4*3] = {
@@ -698,12 +712,457 @@ InitOrientationMatrix(GLfloat matrix[static 4*4],
     }
 }
 
+static void
+sampler_scale_load(const struct vlc_gl_sampler *sampler)
+{
+    struct vlc_gl_sampler_priv *priv = PRIV(sampler);
+    const struct vlc_gl_interop *interop = priv->interop;
+    const opengl_vtable_t *vt = priv->vt;
+
+    /*Default texture setup*/
+    if (priv->yuv_color)
+        vt->UniformMatrix4fv(priv->uloc.ConvMatrix, 1, GL_FALSE, priv->conv_matrix);
+
+    for (unsigned i = 0; i < interop->tex_count; ++i)
+    {
+        vt->Uniform1i(priv->uloc.Textures[i], i);
+
+        assert(priv->textures[i] != 0);
+        vt->ActiveTexture(GL_TEXTURE0 + i);
+        vt->BindTexture(interop->tex_target, priv->textures[i]);
+
+        vt->UniformMatrix3fv(priv->uloc.TexCoordsMaps[i], 1, GL_FALSE,
+                             priv->var.TexCoordsMaps[i]);
+    }
+
+    const GLfloat *tm = GetTransformMatrix(interop);
+    vt->UniformMatrix4fv(priv->uloc.TransformMatrix, 1, GL_FALSE, tm);
+
+    vt->UniformMatrix4fv(priv->uloc.OrientationMatrix, 1, GL_FALSE,
+                         priv->var.OrientationMatrix);
+
+    if (interop->tex_target == GL_TEXTURE_RECTANGLE)
+    {
+        for (unsigned i = 0; i < interop->tex_count; ++i)
+            vt->Uniform2f(priv->uloc.TexSizes[i], priv->tex_widths[i],
+                          priv->tex_heights[i]);
+    }
+
+    /*placebo shader requierements*/
+    const struct pl_shader_res *res = priv->upscale.sh;
+    for (int i = 0; res && i < res->num_variables; i++)
+    {
+        GLint loc = priv->upscale.pl_vars[i];
+        assert(loc != -1);
+
+        struct pl_shader_var sv = res->variables[i];
+        struct pl_var var = sv.var;
+
+        assert(!(var.type != PL_VAR_FLOAT));
+        assert(!(var.dim_m > 1 && var.dim_m != var.dim_v));
+
+        const float *f = sv.data;
+        switch (var.dim_m)
+        {
+            case 4: vt->UniformMatrix4fv(loc, 1, GL_FALSE, f); break;
+            case 3: vt->UniformMatrix3fv(loc, 1, GL_FALSE, f); break;
+            case 2: vt->UniformMatrix2fv(loc, 1, GL_FALSE, f); break;
+
+            case 1:
+                    switch (var.dim_v)
+                    {
+                        case 1: vt->Uniform1f(loc, f[0]); break;
+                        case 2: vt->Uniform2f(loc, f[0], f[1]); break;
+                        case 3: vt->Uniform3f(loc, f[0], f[1], f[2]); break;
+                        case 4: vt->Uniform4f(loc, f[0], f[1], f[2], f[3]); break;
+                    }
+                    break;
+        }
+    }
+
+    for (int i = 0; res && i < res->num_descriptors; i++)
+    {
+        GLint loc = priv->upscale.pl_desc[i];
+        assert(loc != -1);
+
+        vt->ActiveTexture(GL_TEXTURE0 + PICTURE_PLANE_MAX + i );
+        // TODO: bind the correct target_type
+
+        if (i == 0)
+            vt->BindTexture(GL_TEXTURE_1D, priv->upscale.lut_texture);
+        vt->Uniform1i(loc, PICTURE_PLANE_MAX + i);
+     }
+}
+
+static void
+sampler_scale_fetch_locations(struct vlc_gl_sampler *sampler, GLuint program)
+{
+    struct vlc_gl_sampler_priv *priv = PRIV(sampler);
+    const opengl_vtable_t *vt = priv->vt;
+    const struct vlc_gl_interop *interop = priv->interop;
+
+    /*Default texture setup*/
+    if (priv->yuv_color)
+    {
+        priv->uloc.ConvMatrix = vt->GetUniformLocation(program, "ConvMatrix");
+        assert(priv->uloc.ConvMatrix != -1);
+    }
+
+    priv->uloc.TransformMatrix =
+        vt->GetUniformLocation(program, "TransformMatrix");
+    assert(priv->uloc.TransformMatrix != -1);
+
+    priv->uloc.OrientationMatrix =
+        vt->GetUniformLocation(program, "OrientationMatrix");
+    assert(priv->uloc.OrientationMatrix != -1);
+
+    for (unsigned int i = 0; i < interop->tex_count; ++i)
+    {
+        char name[sizeof("TexCoordsMapX")];
+
+        snprintf(name, sizeof(name), "Texture%1u", i);
+        priv->uloc.Textures[i] = vt->GetUniformLocation(program, name);
+        assert(priv->uloc.Textures[i] != -1);
+
+        snprintf(name, sizeof(name), "TexCoordsMap%1u", i);
+        priv->uloc.TexCoordsMaps[i] = vt->GetUniformLocation(program, name);
+        assert(priv->uloc.TexCoordsMaps[i] != -1);
+
+        if (interop->tex_target == GL_TEXTURE_RECTANGLE)
+        {
+            snprintf(name, sizeof(name), "TexSize%1u", i);
+            priv->uloc.TexSizes[i] = vt->GetUniformLocation(program, name);
+            assert(priv->uloc.TexSizes[i] != -1);
+        }
+    }
+
+    const struct pl_shader_res *res = priv->upscale.sh;
+    GLint *pl_vars = priv->upscale.pl_vars;
+    for (int i = 0; i < res->num_variables; i++)
+    {
+        struct pl_shader_var sv = res->variables[i];
+        pl_vars[i] = vt->GetUniformLocation(program, sv.var.name);
+        assert(pl_vars[i] != -1);
+    }
+
+    GLint *pl_desc = priv->upscale.pl_desc;
+    for (int i = 0; i < res->num_descriptors; i++)
+    {
+        struct pl_shader_desc desc = res->descriptors[i];
+        pl_desc[i] = vt->GetUniformLocation(program, desc.desc.name);
+        assert(pl_desc[i] != -1);
+    }
+}
+
+#define ADD(x) vlc_memstream_puts(&ms, x)
+#define ADDF(x, ...) vlc_memstream_printf(&ms, x, ##__VA_ARGS__)
+
+static int
+opengl_init_shader_scale(struct vlc_gl_sampler *sampler, GLenum tex_target,
+                            vlc_fourcc_t chroma, video_color_space_t yuv_space,
+                            video_orientation_t orientation)
+{
+    struct vlc_gl_sampler_priv *priv = PRIV(sampler);
+    struct vlc_gl_interop *interop = priv->interop;
+    const opengl_vtable_t *vt = priv->vt;
+
+    const char *swizzle_per_tex[PICTURE_PLANE_MAX] = { NULL };
+    const bool is_yuv = vlc_fourcc_IsYUV(chroma);
+
+    const vlc_chroma_description_t *desc = vlc_fourcc_GetChromaDescription(chroma);
+    if (desc == NULL)
+        return VLC_EGENERIC;
+
+    InitOrientationMatrix(priv->var.OrientationMatrix, orientation);
+
+    if (is_yuv)
+    {
+        int ret;
+        ret = sampler_yuv_base_init(sampler, chroma, desc, yuv_space);
+        if (ret != VLC_SUCCESS)
+            return ret;
+        ret = opengl_init_swizzle(interop, swizzle_per_tex, chroma, desc);
+        if (ret != VLC_SUCCESS)
+            return ret;
+    }
+
+    const char *sampler_name;
+    switch (tex_target)
+    {
+        case GL_TEXTURE_EXTERNAL_OES:
+            sampler_name = "samplerExternalOES";
+            break;
+        case GL_TEXTURE_2D:
+            sampler_name = "sampler2D";
+            break;
+        case GL_TEXTURE_RECTANGLE:
+            sampler_name = "sampler2DRect";
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+
+    struct pl_plane_data planes[4] = { 0 };
+    int nb_planes = vlc_placebo_PlaneFormat(&interop->fmt_out, planes);
+    assert(nb_planes);
+
+    int num_components = 0;
+    while (planes[0].component_size[num_components])
+        ++num_components;
+
+    struct pl_tex_params tex_params = {
+        .h = interop->fmt_out.i_height * 2,
+        .w = interop->fmt_out.i_width * 2,
+        .d = 0,
+        .format = pl_find_fmt(priv->pl_gpu, PL_FMT_UNORM, num_components, 0, 0, PL_FMT_CAP_RENDERABLE),
+        .sampleable = true,
+        .sample_mode = PL_TEX_SAMPLE_LINEAR,
+        .address_mode = PL_TEX_ADDRESS_CLAMP,
+        .user_data = NULL,
+    };
+    assert(tex_params.format);
+
+    struct pl_sample_src sample_src =
+    {
+        .tex = NULL,
+        .sampler_params = tex_params,
+        .sampled_w = interop->fmt_out.i_width,
+        .sampled_h = interop->fmt_out.i_height,
+        .new_w = 0,
+        .new_h = 0,
+        .scale = 0,
+        .components = num_components,
+    };
+
+    struct pl_shader_obj *lut = NULL;
+    int filter_id = polar_filters[var_InheritInteger(priv->gl, "pl-scale")];
+    struct pl_sample_filter_params filter_params = {
+        .filter = *scale_config[filter_id],
+        .lut = &lut,
+    };
+
+    struct pl_glsl_desc gl_desc = { .version = 120, .gles = false, .vulkan = false, };
+
+    struct pl_shader *shader_color = pl_shader_alloc(priv->pl_ctx,
+        &(struct pl_shader_params) { .id = 0, .gpu = priv->pl_gpu, .glsl = gl_desc, });
+    assert(shader_color);
+
+    struct pl_color_map_params color_params = pl_color_map_default_params;
+    color_params.intent = var_InheritInteger(priv->gl, "rendering-intent");
+    color_params.tone_mapping_algo = var_InheritInteger(priv->gl, "tone-mapping");
+    color_params.tone_mapping_param = var_InheritFloat(priv->gl, "tone-mapping-param");
+    #if PL_API_VER >= 10
+        color_params.desaturation_strength = var_InheritFloat(priv->gl, "desat-strength");
+        color_params.desaturation_exponent = var_InheritFloat(priv->gl, "desat-exponent");
+        color_params.desaturation_base = var_InheritFloat(priv->gl, "desat-base");
+    #else
+        color_params.tone_mapping_desaturate = var_InheritFloat(priv->gl, "tone-mapping-desat");
+    #endif
+        color_params.gamut_warning = var_InheritBool(priv->gl, "tone-mapping-warn");
+
+    struct pl_color_space dst_space = pl_color_space_unknown;
+    dst_space.primaries = var_InheritInteger(priv->gl, "target-prim");
+    dst_space.transfer = var_InheritInteger(priv->gl, "target-trc");
+
+    pl_shader_color_map(shader_color, &color_params,
+            vlc_placebo_ColorSpace(&interop->fmt_out),
+            dst_space, NULL, false);
+
+    struct pl_shader_obj *dither_state = NULL;
+    int method = var_InheritInteger(priv->gl, "dither-algo");
+    if (method >= 0)
+    {
+        unsigned out_bits = 0;
+        int override = var_InheritInteger(priv->gl, "dither-depth");
+        if (override > 0)
+            out_bits = override;
+        else
+        {
+            GLint fb_depth = 0;
+            #if !defined(USE_OPENGL_ES2)
+                   /* fetch framebuffer depth (we are already bound to the default one). */
+                   if (vt->GetFramebufferAttachmentParameteriv != NULL)
+                       vt->GetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_BACK_LEFT,
+                                                               GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE,
+                                                               &fb_depth);
+            #endif
+            if (fb_depth <= 0)
+                fb_depth = 8;
+            out_bits = fb_depth;
+         }
+
+         pl_shader_dither(shader_color, out_bits, &dither_state, &(struct pl_dither_params) {
+             .method = method,
+         });
+         pl_shader_obj_destroy(&dither_state);
+    }
+    priv->upscale.sh_color = pl_shader_finalize(shader_color);
+
+    GLenum texture_type;
+    const char *sampler_lut;
+    assert(filter_params.filter.polar);
+    if (filter_params.filter.polar)
+    {
+        struct pl_shader *shader = pl_shader_alloc(priv->pl_ctx,
+                &(struct pl_shader_params) { .id = 0, .gpu = priv->pl_gpu, .glsl = gl_desc, });
+        assert(shader);
+
+        pl_shader_sample_polar(shader, &sample_src, &filter_params);
+        priv->upscale.sh = pl_shader_finalize(shader);
+        texture_type = GL_TEXTURE_1D;
+        sampler_lut = "sampler1D";
+    }
+
+    const struct pl_shader_res *result = priv->upscale.sh;
+    assert(result);
+    float *data = NULL;
+    unsigned lut_w, lut_h;
+    for (int n = 0; n < result->num_descriptors; n++)
+    {
+      const struct pl_shader_desc *sd = &result->descriptors[n];
+
+      if(sd->desc.type != PL_DESC_SAMPLED_TEX)
+          continue;
+
+      const struct pl_tex *tex = sd->object;
+      data = (float *) pl_tex_dummy_data(tex);
+      lut_w = tex->params.w;
+      lut_h = tex->params.h;
+      if (data)
+          break;
+    }
+    assert(data);
+
+    vt->ActiveTexture(GL_TEXTURE0 + PICTURE_PLANE_MAX);
+    vt->GenTextures(1, &priv->upscale.lut_texture);
+    assert(lut_h == 0);
+    vt->BindTexture(texture_type, priv->upscale.lut_texture);
+    vt->TexImage1D(texture_type, 0, GL_RGBA, lut_w, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+    vt->TexParameteri(texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    vt->TexParameteri(texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    vt->TexParameteri(texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    vt->TexParameteri(texture_type, GL_TEXTURE_WRAP_T, GL_REPEAT);
+    vt->ActiveTexture(GL_TEXTURE0);
+
+    struct vlc_memstream ms;
+    if (vlc_memstream_open(&ms) != 0)
+        return VLC_EGENERIC;
+
+    if (is_yuv)
+        ADD("uniform mat4 ConvMatrix;\n");
+
+    ADD("uniform mat4 TransformMatrix;\n"
+        "uniform mat4 OrientationMatrix;\n");
+    for (unsigned i = 0; i < interop->tex_count; ++i)
+        ADDF("uniform %s Texture%u;\n"
+             "uniform mat3 TexCoordsMap%u;\n", sampler_name, i, i);
+
+    priv->upscale.pl_vars = calloc(priv->upscale.sh->num_variables, sizeof(GLint));
+    priv->upscale.pl_desc = calloc(priv->upscale.sh->num_descriptors, sizeof(GLint));
+
+    for (int n = 0; n < result->num_variables; n++)
+    {
+        const struct pl_shader_var *shader_var = &result->variables[n];
+        const struct pl_var *var = &shader_var->var;
+
+        ADDF("uniform %s %s;\n", pl_var_glsl_type_name(*var), var->name);
+    }
+
+    for (int n = 0; n < result->num_vertex_attribs; n++)
+    {
+        const struct pl_shader_va   *vertex_attribs = &result->vertex_attribs[n];
+        const struct pl_vertex_attrib *attr = &vertex_attribs->attr;
+        const struct pl_fmt *fmt = attr->fmt;
+        const char *name = attr->name;
+
+        ADDF("uniform %s %s;\n", fmt->glsl_type, name);
+    }
+
+    for (int n = 0; n < result->num_descriptors; n++)
+    {
+        const struct pl_shader_desc *sd = &result->descriptors[n];
+        const struct pl_desc *sh_desc = &sd->desc;
+
+        ADDF("uniform %s %s;\n", sampler_lut, sh_desc->name);
+    }
+    ADDF("%s", priv->upscale.sh->glsl);
+    ADDF("%s", priv->upscale.sh_color->glsl);
+
+    ADD("vec4 vlc_texture(vec2 pic_coords) {\n"
+        " vec3 pic_hcoords = vec3((TransformMatrix * OrientationMatrix * "
+        "vec4(pic_coords, 0.0, 1.0)).st, 1.0);\n"
+        " vec2 tex_coords;\n");
+
+
+    unsigned color_count;
+    if (is_yuv) {
+        ADD(" vec4 texel;\n"
+            " vec4 pixel = vec4(0.0, 0.0, 0.0, 1.0);\n");
+
+        unsigned color_idx = 0;
+        for (unsigned i = 0; i < interop->tex_count; ++i)
+        {
+            const char *swizzle = swizzle_per_tex[i];
+            assert(swizzle);
+            size_t swizzle_count = strlen(swizzle);
+            ADDF(" tex_coords = (TexCoordsMap%u * pic_hcoords).st;\n", i);
+            if (tex_target == GL_TEXTURE_RECTANGLE)
+            {
+                /* The coordinates are in texels values, not normalized */
+                ADDF(" tex_coords = vec2(tex_coords.x * TexSize%u.x,\n"
+                     "                   tex_coords.y * TexSize%u.y);\n", i, i);
+            }
+            ADDF(" texel = %s(Texture%.1u, tex_coords);\n", priv->upscale.sh->name, i);
+            for (unsigned j = 0; j < swizzle_count; ++j)
+            {
+                ADDF(" pixel[%u] = texel.%c;\n", color_idx, swizzle[j]);
+                color_idx++;
+                assert(color_idx <= PICTURE_PLANE_MAX);
+            }
+        }
+        ADD(" vec4 result = ConvMatrix * pixel;\n");
+        color_count = color_idx;
+    }
+    else
+    {
+        ADD(" tex_coords = (TexCoordsMap0 * pic_hcoords).st;\n");
+        ADDF(" vec4 result = %s(Texture0, tex_coords);\n", priv->upscale.sh->name);
+        color_count = 1;
+    }
+    assert(yuv_space == COLOR_SPACE_UNDEF || color_count == 3);
+
+    ADDF(" return result = %s(result);\n", priv->upscale.sh_color->name);
+    ADD("}\n");
+#undef ADD
+#undef ADDF
+
+    if (vlc_memstream_close(&ms) != 0)
+        return VLC_EGENERIC;
+
+    sampler->shader.extensions = NULL;
+    sampler->shader.body = ms.ptr;
+
+    static const struct vlc_gl_sampler_ops ops = {
+        .fetch_locations = sampler_scale_fetch_locations,
+        .load = sampler_scale_load,
+    };
+    sampler->ops = &ops;
+
+    return VLC_SUCCESS;
+}
+
 static int
 opengl_fragment_shader_init(struct vlc_gl_sampler *sampler, GLenum tex_target,
                             vlc_fourcc_t chroma, video_color_space_t yuv_space,
                             video_orientation_t orientation)
 {
     struct vlc_gl_sampler_priv *priv = PRIV(sampler);
+    int scale_filter = var_InheritInteger(priv->gl, "pl-scale");
+    if(scale_filter > 0 && scale_filter < 6)
+    {
+        return opengl_init_shader_scale(sampler, tex_target, chroma, yuv_space,
+                                        orientation);
+    }
 
     struct vlc_gl_interop *interop = priv->interop;
 
@@ -971,6 +1430,7 @@ vlc_gl_sampler_New(struct vlc_gl_interop *interop)
 #       endif
             .vulkan = false,
         },
+        .limits = { 0 },
     };
 
     const opengl_vtable_t *vt = interop->vt;
@@ -1076,6 +1536,12 @@ vlc_gl_sampler_Delete(struct vlc_gl_sampler *sampler)
 
 #ifdef HAVE_LIBPLACEBO
     FREENULL(priv->uloc.pl_vars);
+    if (priv->upscale.pl_vars)
+        FREENULL(priv->upscale.pl_vars);
+    if (priv->upscale.pl_vars)
+        FREENULL(priv->upscale.pl_desc);
+    if (priv->pl_gpu)
+        pl_gpu_dummy_destroy(&priv->pl_gpu);
     if (priv->pl_ctx)
         pl_context_destroy(&priv->pl_ctx);
 #endif
diff --git a/modules/video_output/opengl/vout_helper.h b/modules/video_output/opengl/vout_helper.h
index 482eabb554..3937c1e3e6 100644
--- a/modules/video_output/opengl/vout_helper.h
+++ b/modules/video_output/opengl/vout_helper.h
@@ -51,6 +51,9 @@
 #endif
 
 #define add_glopts_placebo() \
+    set_section(N_("Scaling algorithm"), NULL) \
+    add_integer("pl-scale", 0, SCALE_FILTER_TEXT, SCALE_FILTER_LONGTEXT, false) \
+        change_integer_list(upscale_filters_values, upscale_filters_text) \
     set_section(N_("Colorspace conversion"), NULL) \
     add_integer("rendering-intent", pl_color_map_default_params.intent, \
                 RENDER_INTENT_TEXT, RENDER_INTENT_LONGTEXT, false) \
diff --git a/modules/video_output/placebo_utils.h b/modules/video_output/placebo_utils.h
index 99c3f318b5..cdfb040f16 100644
--- a/modules/video_output/placebo_utils.h
+++ b/modules/video_output/placebo_utils.h
@@ -330,6 +330,29 @@ static const struct pl_filter_config *const scale_config[] = {
     [SCALE_CUSTOM]              = NULL,
 };
 
+#define SCALE_FILTER_TEXT "Upscale filters"
+#define SCALE_FILTER_LONGTEXT "Upscale filters"
+
+static const int upscale_filters_values[] = {
+     0,
+     1,
+     2,
+     3,
+     4,
+     5,
+};
+
+static const char * const upscale_filters_text[] = {
+    "No filter",
+    "Unwindowed EWA Jinc (clipped)",
+    "Jinc / EWA Lanczos 3 taps (high quality, slow)",
+    "EWA Ginseng",
+    "EWA Hann",
+    "HaasnSoft (blurred EWA Hann)",
+    "EWA Robidoux",
+    "EWA RobidouxSharp",
+};
+
 #define UPSCALER_PRESET_TEXT "Upscaler preset"
 #define DOWNSCALER_PRESET_TEXT "Downscaler preset"
 #define SCALER_PRESET_LONGTEXT "Choose from one of the built-in scaler presets. If set to custom, you can choose your own combination of kernel/window functions."
-- 
2.28.0



More information about the vlc-devel mailing list