Commit 77d8be7e authored by Niklas Haas's avatar Niklas Haas Committed by 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's avatarThomas Guillem <thomas@gllm.fr>
parent f023eb4b
......@@ -4082,6 +4082,26 @@ dnl Libnotify notification plugin
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
......
......@@ -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)'
......
......@@ -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;
......
......@@ -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;"
"}");
......
......@@ -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)
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment