[vlc-commits] video_output: opengl: add basic support for libplacebo

Niklas Haas git at videolan.org
Fri Nov 3 11:11:47 CET 2017


vlc | branch: master | Niklas Haas <git at haasn.xyz> | Tue Oct 31 22:04:49 2017 +0100| [77d8be7e6387a81bc1749d8a7fb62a366bd652d6] | committer: Thomas Guillem

video_output: opengl: add basic support for libplacebo

This adds support for conversion between color spaces, in particular HDR
tone mapping and wide gamut -> standard gamut conversion.

Things that this commit does not (yet) address:

- It does not use libplacebo's scaling features. This is blocked by the
  fact that libplacebo does not currently support OpenGL (only Vulkan).

- It does not add support for BT.2020 YCbCr. This would be easy to
  implement in much the same way as the tone mapping support, by using
  pl_shader_decode_color, replacing the hard-coded matrices. This would
  also allow adding support for hue, saturation etc. controls.

- It does not hook up the pl_color_map_params options to the VLC GUI.
  This would have to be done to let users influence the subjective
  configuration options, as well as specify their display device's
  configuration.

All of the new code is optional, due to the lack of rigorous testing of
libplacebo (especially on other platforms) and the short timeframe
between this commit and the VLC 3.0 release. This may be changed later
on (possibly for VLC 4.0).

Signed-off-by: Thomas Guillem <thomas at gllm.fr>

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=77d8be7e6387a81bc1749d8a7fb62a366bd652d6
---

 configure.ac                                   |  20 ++++
 modules/video_output/Makefile.am               |  24 +++--
 modules/video_output/opengl/converter.h        |  15 +++
 modules/video_output/opengl/fragment_shaders.c | 140 +++++++++++++++++++++++++
 modules/video_output/opengl/vout_helper.c      |  31 ++++++
 5 files changed, 224 insertions(+), 6 deletions(-)

diff --git a/configure.ac b/configure.ac
index f4e5aa9d30..7e4e90bb14 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4083,6 +4083,26 @@ dnl
 PKG_ENABLE_MODULES_VLC([NOTIFY], [], [libnotify gtk+-2.0], [libnotify notification], [auto])
 
 dnl
+dnl  libplacebo support
+dnl
+AC_ARG_ENABLE(libplacebo,
+  [AS_HELP_STRING([--disable-libplacebo],
+      [disable libplacebo support (default auto)])])
+
+AS_IF([test "$enable_libplacebo" != "no"], [
+  PKG_CHECK_MODULES([LIBPLACEBO], [libplacebo >= 0.1], [
+    AC_DEFINE([HAVE_LIBPLACEBO], [1], [Define to 1 if libplacebo is enabled.])
+  ], [
+    AS_IF([test -n "${enable_libplacebo}"], [
+      AC_MSG_ERROR([${LIBPLACEBO_PKG_ERRORS}.])
+    ])
+    enable_libplacebo="no"
+  ])
+])
+AM_CONDITIONAL(HAVE_LIBPLACEBO, [test "$enable_libplacebo" != "no"])
+
+
+dnl
 dnl  Endianness check
 dnl
 AC_C_BIGENDIAN
diff --git a/modules/video_output/Makefile.am b/modules/video_output/Makefile.am
index b77824944d..2785504d8c 100644
--- a/modules/video_output/Makefile.am
+++ b/modules/video_output/Makefile.am
@@ -8,6 +8,9 @@ OPENGL_COMMONSOURCES = video_output/opengl/vout_helper.c \
 	video_output/opengl/internal.h video_output/opengl/fragment_shaders.c \
 	video_output/opengl/converter_sw.c
 
+OPENGL_COMMONCLFAGS = $(LIBPLACEBO_CFLAGS)
+OPENGL_COMMONLIBS = $(LIBPLACEBO_LIBS)
+
 if HAVE_DECKLINK
 libdecklinkoutput_plugin_la_SOURCES = video_output/decklink.cpp
 libdecklinkoutput_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(CPPFLAGS_decklinkoutput)
@@ -21,10 +24,14 @@ libglconv_cvpx_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)'
 
 if HAVE_OSX
 libvout_macosx_plugin_la_SOURCES = video_output/macosx.m $(OPENGL_COMMONSOURCES)
+libvout_macosx_plugin_la_CFLAGS = $(AM_CFLAGS) $(OPENGL_COMMONCLFAGS)
+libvout_macosx_plugin_la_LIBADD = $(OPENGL_COMMONLIBS)
 libvout_macosx_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' \
 	-Wl,-framework,OpenGL,-framework,Cocoa
 
 libcaopengllayer_plugin_la_SOURCES = video_output/caopengllayer.m $(OPENGL_COMMONSOURCES)
+libcaopengllayer_plugin_la_CFLAGS = $(AM_CFLAGS) $(OPENGL_COMMONCLFAGS)
+libcaopengllayer_plugin_la_LIBADD = $(OPENGL_COMMONLIBS)
 libcaopengllayer_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' \
 	-Wl,-framework,OpenGL,-framework,Cocoa,-framework,QuartzCore
 
@@ -39,6 +46,8 @@ libglconv_cvpx_plugin_la_CFLAGS = $(AM_CFLAGS) -DUSE_OPENGL_ES2
 endif
 
 libvout_ios_plugin_la_SOURCES = video_output/ios.m $(OPENGL_COMMONSOURCES)
+libvout_ios_plugin_la_CFLAGS = $(AM_CFLAGS) $(OPENGL_COMMONCLFAGS)
+libvout_ios_plugin_la_LIBADD = $(OPENGL_COMMONLIBS)
 libvout_ios_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)' \
 	-Wl,-framework,OpenGLES,-framework,QuartzCore,-framework,UIKit
 if HAVE_IOS
@@ -50,16 +59,16 @@ endif
 
 ### OpenGL ###
 libgles2_plugin_la_SOURCES = $(OPENGL_COMMONSOURCES) video_output/opengl/display.c
-libgles2_plugin_la_CFLAGS = $(AM_CFLAGS) $(GLES2_CFLAGS) -DUSE_OPENGL_ES2
-libgles2_plugin_la_LIBADD = $(GLES2_LIBS) $(LIBM)
+libgles2_plugin_la_CFLAGS = $(AM_CFLAGS) $(GLES2_CFLAGS) -DUSE_OPENGL_ES2 $(OPENGL_COMMONCLFAGS)
+libgles2_plugin_la_LIBADD = $(GLES2_LIBS) $(LIBM) $(OPENGL_COMMONLIBS)
 libgles2_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)'
 
 EXTRA_LTLIBRARIES += libgles2_plugin.la
 vout_LTLIBRARIES += $(LTLIBgles2)
 
 libgl_plugin_la_SOURCES = $(OPENGL_COMMONSOURCES) video_output/opengl/display.c
-libgl_plugin_la_CFLAGS = $(AM_CFLAGS) $(GL_CFLAGS)
-libgl_plugin_la_LIBADD = $(GL_LIBS) $(LIBM)
+libgl_plugin_la_CFLAGS = $(AM_CFLAGS) $(GL_CFLAGS) $(OPENGL_COMMONCLFAGS)
+libgl_plugin_la_LIBADD = $(GL_LIBS) $(LIBM) $(OPENGL_COMMONLIBS)
 
 libglconv_vaapi_wl_plugin_la_SOURCES = video_output/opengl/converter_vaapi.c \
 	video_output/opengl/converter.h \
@@ -324,8 +333,11 @@ libglwin32_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) \
 libwgl_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) \
         -DMODULE_NAME_IS_wgl
 
-libglwin32_plugin_la_LIBADD = -lopengl32 -lgdi32 $(LIBCOM) -luuid
-libwgl_plugin_la_LIBADD = -lopengl32 -lgdi32
+libglwin32_plugin_la_LIBADD = -lopengl32 -lgdi32 $(LIBCOM) -luuid $(OPENGL_COMMONLIBS)
+libwgl_plugin_la_LIBADD = -lopengl32 -lgdi32 $(OPENGL_COMMONLIBS)
+
+libglwin32_plugin_la_CFLAGS = $(AM_CFLAGS) $(OPENGL_COMMONCLFAGS)
+libwgl_plugin_la_CFLAGS = $(AM_CFLAGS) $(OPENGL_COMMONCLFAGS)
 
 libglwin32_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)'
 libwgl_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(voutdir)'
diff --git a/modules/video_output/opengl/converter.h b/modules/video_output/opengl/converter.h
index 4be26b573d..efb423ae69 100644
--- a/modules/video_output/opengl/converter.h
+++ b/modules/video_output/opengl/converter.h
@@ -21,6 +21,10 @@
 #ifndef VLC_OPENGL_CONVERTER_H
 #define VLC_OPENGL_CONVERTER_H
 
+#ifdef HAVE_LIBPLACEBO
+#include <libplacebo/shaders.h>
+#endif
+
 #include "vout_helper.h"
 #include <vlc_plugin.h>
 
@@ -198,6 +202,11 @@ struct opengl_tex_converter_t
     /* Pointer to object gl, set by the caller */
     vlc_gl_t *gl;
 
+#ifdef HAVE_LIBPLACEBO
+    /* libplacebo context, created by the caller (optional) */
+    struct pl_context *pl_ctx;
+#endif
+
     /* Function pointers to OpenGL functions, set by the caller */
     const opengl_vtable_t *vt;
 
@@ -252,10 +261,16 @@ struct opengl_tex_converter_t
         GLint TexSize[PICTURE_PLANE_MAX]; /* for GL_TEXTURE_RECTANGLE */
         GLint Coefficients;
         GLint FillColor;
+        GLint *pl_vars; /* for pl_sh_res */
     } uloc;
     bool yuv_color;
     GLfloat yuv_coefficients[16];
 
+#ifdef HAVE_LIBPLACEBO
+    struct pl_shader *pl_sh;
+    const struct pl_shader_res *pl_sh_res;
+#endif
+
     /* Private context */
     void *priv;
 
diff --git a/modules/video_output/opengl/fragment_shaders.c b/modules/video_output/opengl/fragment_shaders.c
index 5de7b95359..9516dbd767 100644
--- a/modules/video_output/opengl/fragment_shaders.c
+++ b/modules/video_output/opengl/fragment_shaders.c
@@ -25,6 +25,11 @@
 #include <assert.h>
 #include <stdlib.h>
 
+#ifdef HAVE_LIBPLACEBO
+#include <libplacebo/shaders.h>
+#include <libplacebo/shaders/colorspace.h>
+#endif
+
 #include <vlc_common.h>
 #include <vlc_memstream.h>
 #include "internal.h"
@@ -314,6 +319,15 @@ tc_base_fetch_locations(opengl_tex_converter_t *tc, GLuint program)
     tc->uloc.FillColor = tc->vt->GetUniformLocation(program, "FillColor");
     if (tc->uloc.FillColor == -1)
         return VLC_EGENERIC;
+
+#ifdef HAVE_LIBPLACEBO
+    const struct pl_shader_res *res = tc->pl_sh_res;
+    for (int i = 0; res && i < res->num_variables; i++) {
+        struct pl_shader_var sv = res->variables[i];
+        tc->uloc.pl_vars[i] = tc->vt->GetUniformLocation(program, sv.var.name);
+    }
+#endif
+
     return VLC_SUCCESS;
 }
 
@@ -338,6 +352,40 @@ tc_base_prepare_shader(const opengl_tex_converter_t *tc,
             tc->vt->Uniform2f(tc->uloc.TexSize[i], tex_width[i],
                                tex_height[i]);
     }
+
+#ifdef HAVE_LIBPLACEBO
+    const struct pl_shader_res *res = tc->pl_sh_res;
+    for (int i = 0; res && i < res->num_variables; i++) {
+        GLint loc = tc->uloc.pl_vars[i];
+        if (loc == -1) // uniform optimized out
+            continue;
+
+        struct pl_shader_var sv = res->variables[i];
+        struct ra_var var = sv.var;
+
+        // libplacebo doesn't need anything else anyway
+        if (var.type != RA_VAR_FLOAT)
+            continue;
+        if (var.dim_m > 1 && var.dim_m != var.dim_v)
+            continue;
+
+        const float *f = sv.data;
+        switch (var.dim_m) {
+        case 4: tc->vt->UniformMatrix4fv(loc, 1, GL_FALSE, f); break;
+        case 3: tc->vt->UniformMatrix3fv(loc, 1, GL_FALSE, f); break;
+        case 2: tc->vt->UniformMatrix2fv(loc, 1, GL_FALSE, f); break;
+
+        case 1:
+            switch (var.dim_v) {
+            case 1: tc->vt->Uniform1f(loc, f[0]); break;
+            case 2: tc->vt->Uniform2f(loc, f[0], f[1]); break;
+            case 3: tc->vt->Uniform3f(loc, f[0], f[1], f[2]); break;
+            case 4: tc->vt->Uniform4f(loc, f[0], f[1], f[2], f[3]); break;
+            }
+            break;
+        }
+    }
+#endif
 }
 
 static int
@@ -411,6 +459,57 @@ xyz12_shader_init(opengl_tex_converter_t *tc)
     return fragment_shader;
 }
 
+#ifdef HAVE_LIBPLACEBO
+static struct pl_color_space pl_color_space_from_video_format(video_format_t fmt)
+{
+    static enum pl_color_primaries primaries[COLOR_PRIMARIES_MAX+1] = {
+        [COLOR_PRIMARIES_UNDEF]     = PL_COLOR_PRIM_UNKNOWN,
+        [COLOR_PRIMARIES_BT601_525] = PL_COLOR_PRIM_BT_601_525,
+        [COLOR_PRIMARIES_BT601_625] = PL_COLOR_PRIM_BT_601_625,
+        [COLOR_PRIMARIES_BT709]     = PL_COLOR_PRIM_BT_709,
+        [COLOR_PRIMARIES_BT2020]    = PL_COLOR_PRIM_BT_2020,
+        [COLOR_PRIMARIES_DCI_P3]    = PL_COLOR_PRIM_DCI_P3,
+        [COLOR_PRIMARIES_BT470_M]   = PL_COLOR_PRIM_BT_470M,
+    };
+
+    static enum pl_color_transfer transfers[TRANSFER_FUNC_MAX+1] = {
+        [TRANSFER_FUNC_UNDEF]        = PL_COLOR_TRC_UNKNOWN,
+        [TRANSFER_FUNC_LINEAR]       = PL_COLOR_TRC_LINEAR,
+        [TRANSFER_FUNC_SRGB]         = PL_COLOR_TRC_SRGB,
+        [TRANSFER_FUNC_SMPTE_ST2084] = PL_COLOR_TRC_PQ,
+        [TRANSFER_FUNC_HLG]          = PL_COLOR_TRC_HLG,
+        // these are all designed to be displayed on BT.1886 displays, so this
+        // is the correct way to handle them in libplacebo
+        [TRANSFER_FUNC_BT470_BG]    = PL_COLOR_TRC_BT_1886,
+        [TRANSFER_FUNC_BT470_M]     = PL_COLOR_TRC_BT_1886,
+        [TRANSFER_FUNC_BT709]       = PL_COLOR_TRC_BT_1886,
+        [TRANSFER_FUNC_SMPTE_240]   = PL_COLOR_TRC_BT_1886,
+    };
+
+    // Derive the signal peak/avg from the color light level metadata
+    float sig_peak = fmt.lighting.MaxCLL / PL_COLOR_REF_WHITE;
+    float sig_avg = fmt.lighting.MaxFALL / PL_COLOR_REF_WHITE;
+
+    // As a fallback value for the signal peak, we can also use the mastering
+    // metadata's luminance information
+    if (!sig_peak)
+        sig_peak = fmt.mastering.max_luminance / PL_COLOR_REF_WHITE;
+
+    // Sanitize the sig_peak/sig_avg, because of buggy or low quality tagging
+    // that's sadly common in lots of typical sources
+    sig_peak = (sig_peak > 1.0 && sig_peak <= 100.0) ? sig_peak : 0.0;
+    sig_avg  = (sig_avg >= 0.0 && sig_avg <= 1.0) ? sig_avg : 0.0;
+
+    return (struct pl_color_space) {
+        .primaries = primaries[fmt.primaries],
+        .transfer  = transfers[fmt.transfer],
+        .light     = PL_COLOR_LIGHT_UNKNOWN,
+        .sig_peak  = sig_peak,
+        .sig_avg   = sig_avg,
+    };
+}
+#endif
+
 GLuint
 opengl_fragment_shader_init_impl(opengl_tex_converter_t *tc, GLenum tex_target,
                                  vlc_fourcc_t chroma, video_color_space_t yuv_space)
@@ -462,6 +561,38 @@ opengl_fragment_shader_init_impl(opengl_tex_converter_t *tc, GLenum tex_target,
         ADDF("uniform %s Texture%u;"
              "varying vec2 TexCoord%u;", sampler, i, i);
 
+#ifdef HAVE_LIBPLACEBO
+    if (tc->pl_sh) {
+        struct pl_shader *sh = tc->pl_sh;
+        pl_shader_color_map(sh, &pl_color_map_default_params,
+                pl_color_space_from_video_format(tc->fmt),
+                pl_color_space_unknown, false);
+
+        const struct pl_shader_res *res = tc->pl_sh_res = pl_shader_finalize(sh);
+
+        FREENULL(tc->uloc.pl_vars);
+        tc->uloc.pl_vars = calloc(res->num_variables, sizeof(GLint));
+        for (int i = 0; i < res->num_variables; i++) {
+            struct pl_shader_var sv = res->variables[i];
+            ADDF("uniform %s %s;", ra_var_glsl_type_name(sv.var), sv.var.name);
+        }
+
+        // We can't handle these yet, but nothing we use requires them, either
+        assert(res->num_vertex_attribs == 0);
+        assert(res->num_descriptors == 0);
+
+        ADD(res->glsl);
+    }
+#else
+    if (tc->fmt.transfer == TRANSFER_FUNC_SMPTE_ST2084 ||
+        tc->fmt.primaries == COLOR_PRIMARIES_BT2020)
+    {
+        // no warning for HLG because it's more or less backwards-compatible
+        msg_Warn(tc->gl, "VLC needs to be built with support for libplacebo "
+                 "in order to display wide gamut or HDR signals correctly.");
+    }
+#endif
+
     if (tex_target == GL_TEXTURE_RECTANGLE)
     {
         for (unsigned i = 0; i < tc->tex_count; ++i)
@@ -532,6 +663,15 @@ opengl_fragment_shader_init_impl(opengl_tex_converter_t *tc, GLenum tex_target,
             ADDF("result = color%u + result;", color_idx);
     }
 
+#ifdef HAVE_LIBPLACEBO
+    if (tc->pl_sh_res) {
+        const struct pl_shader_res *res = tc->pl_sh_res;
+        assert(res->input  == PL_SHADER_SIG_COLOR);
+        assert(res->output == PL_SHADER_SIG_COLOR);
+        ADDF("result = %s(result);", res->name);
+    }
+#endif
+
     ADD("gl_FragColor = result * FillColor;"
         "}");
 
diff --git a/modules/video_output/opengl/vout_helper.c b/modules/video_output/opengl/vout_helper.c
index 4c9b9fd22b..556a9be728 100644
--- a/modules/video_output/opengl/vout_helper.c
+++ b/modules/video_output/opengl/vout_helper.c
@@ -566,7 +566,27 @@ opengl_deinit_program(vout_display_opengl_t *vgl, struct prgm *prgm)
     vlc_object_release(tc);
     if (prgm->id != 0)
         vgl->vt.DeleteProgram(prgm->id);
+
+#ifdef HAVE_LIBPLACEBO
+    FREENULL(tc->uloc.pl_vars);
+    pl_context_destroy(&tc->pl_ctx);
+#endif
+}
+
+#ifdef HAVE_LIBPLACEBO
+static void
+log_cb(void *priv, enum pl_log_level level, const char *msg)
+{
+    opengl_tex_converter_t *tc = priv;
+    switch (level) {
+    case PL_LOG_FATAL: // fall through
+    case PL_LOG_ERR:  msg_Err(tc->gl, "%s", msg); break;
+    case PL_LOG_WARN: msg_Warn(tc->gl,"%s", msg); break;
+    case PL_LOG_INFO: msg_Info(tc->gl,"%s", msg); break;
+    default: break;
+    }
 }
+#endif
 
 static int
 opengl_init_program(vout_display_opengl_t *vgl, struct prgm *prgm,
@@ -592,6 +612,17 @@ opengl_init_program(vout_display_opengl_t *vgl, struct prgm *prgm,
 #endif
     tc->fmt = *fmt;
 
+#ifdef HAVE_LIBPLACEBO
+    // create the main libplacebo context
+    tc->pl_ctx = pl_context_create(PL_API_VER, &(struct pl_context_params) {
+        .log_cb    = log_cb,
+        .log_priv  = tc,
+        .log_level = PL_LOG_INFO,
+    });
+    if (tc->pl_ctx)
+        tc->pl_sh = pl_shader_alloc(tc->pl_ctx, NULL, 0);
+#endif
+
     int ret;
     if (subpics)
     {



More information about the vlc-commits mailing list