/*****************************************************************************
* sampler.c: built-in OpenGL sampler module
*****************************************************************************
* Copyright (C) 2020 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sampler.h"
#include <vlc_common.h>
#include <vlc_memstream.h>
#include <vlc_opengl.h>
#include <vlc_fs.h>
#ifdef HAVE_LIBPLACEBO_GL
#include <libplacebo/opengl.h>
#include <libplacebo/shaders.h>
#include <libplacebo/shaders/colorspace.h>
#include "../libplacebo/utils.h"
#endif
#include "gl_api.h"
#include "gl_common.h"
#include "gl_util.h"
struct sampler_priv {
struct vlc_gl_t *gl;
struct vlc_gl_api api;
const opengl_vtable_t *vt; /* for convenience, same as &api.vt */
struct vlc_gl_picture pic;
struct {
GLint Textures[PICTURE_PLANE_MAX];
GLint TexSizes[PICTURE_PLANE_MAX]; /* for GL_TEXTURE_RECTANGLE */
GLint ConvMatrix;
GLint *pl_vars, *pl_descs; /* for pl_sh_res */
} uloc;
bool yuv_color;
bool unsigned_sampler;
GLfloat conv_matrix[4*4];
#ifdef HAVE_LIBPLACEBO_GL
/* libplacebo context */
pl_log pl_log;
pl_opengl pl_opengl;
pl_shader pl_sh;
pl_shader_obj dither_state, tone_map_state, lut_state;
const struct pl_shader_res *pl_sh_res;
#endif
/* If set, vlc_texture() exposes a single plane (without chroma
* conversion), selected by vlc_gl_sampler_SetCurrentPlane(). */
bool expose_planes;
unsigned plane;
int glsl_version;
struct vlc_gl_extension_vt extension_vt;
};
static inline struct sampler_priv *
PRIV(struct vlc_gl_sampler *sampler)
{
return sampler->sys;
}
static int
sampler_update(struct vlc_gl_sampler *sampler,
const struct vlc_gl_picture *picture)
{
struct sampler_priv *priv = PRIV(sampler);
priv->pic = *picture;
return VLC_SUCCESS;
}
static void
sampler_select_plane(struct vlc_gl_sampler *sampler, unsigned plane)
{
struct sampler_priv *priv = PRIV(sampler);
priv->plane = plane;
}
static void
sampler_close(struct vlc_gl_sampler *sampler)
{
#ifdef HAVE_LIBPLACEBO_GL
struct sampler_priv *priv = PRIV(sampler);
FREENULL(priv->uloc.pl_vars);
FREENULL(priv->uloc.pl_descs);
pl_shader_free(&priv->pl_sh);
pl_shader_obj_destroy(&priv->lut_state);
pl_shader_obj_destroy(&priv->tone_map_state);
pl_shader_obj_destroy(&priv->dither_state);
pl_opengl_destroy(&priv->pl_opengl);
pl_log_destroy(&priv->pl_log);
#endif
free(sampler->sys);
}
static const float MATRIX_COLOR_RANGE_LIMITED[4*3] = {
255.0/219, 0, 0, -255.0/219 * 16.0/255,
0, 255.0/224, 0, -255.0/224 * 128.0/255,
0, 0, 255.0/224, -255.0/224 * 128.0/255,
};
static const float MATRIX_COLOR_RANGE_FULL[4*3] = {
1, 0, 0, 0,
0, 1, 0, -128.0/255,
0, 0, 1, -128.0/255,
};
/*
* Construct the transformation matrix from the luma weight of the red and blue
* component (the green component is deduced).
*/
#define MATRIX_YUV_TO_RGB(KR, KB) \
MATRIX_YUV_TO_RGB_(KR, (1-(KR)-(KB)), KB)
/*
* Construct the transformation matrix from the luma weight of the RGB
* components.
*
* KR: luma weight of the red component
* KG: luma weight of the green component
* KB: luma weight of the blue component
*
* By definition, KR + KG + KB == 1.
*
* Ref: <https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion>
* Ref: libplacebo: src/colorspace.c:luma_coeffs()
* */
#define MATRIX_YUV_TO_RGB_(KR, KG, KB) { \
1, 0, 2*(1.0-(KR)), \
1, -2*(1.0-(KB))*((KB)/(KG)), -2*(1.0-(KR))*((KR)/(KG)), \
1, 2*(1.0-(KB)), 0, \
}
static const float MATRIX_BT601[3*3] = MATRIX_YUV_TO_RGB(0.299, 0.114);
static const float MATRIX_BT709[3*3] = MATRIX_YUV_TO_RGB(0.2126, 0.0722);
static const float MATRIX_BT2020[3*3] = MATRIX_YUV_TO_RGB(0.2627, 0.0593);
static void
init_conv_matrix(float conv_matrix_out[],
video_color_space_t color_space,
video_color_range_t color_range)
{
const float *space_matrix;
switch (color_space) {
case COLOR_SPACE_BT601:
space_matrix = MATRIX_BT601;
break;
case COLOR_SPACE_BT2020:
space_matrix = MATRIX_BT2020;
break;
default:
space_matrix = MATRIX_BT709;
}
/* Init the conversion matrix in column-major order (OpenGL expects
* column-major order by default, and OpenGL ES does not support row-major
* order at all). */
const float *range_matrix = color_range == COLOR_RANGE_FULL
? MATRIX_COLOR_RANGE_FULL
: MATRIX_COLOR_RANGE_LIMITED;
/* Multiply the matrices on CPU once for all */
for (int x = 0; x < 4; ++x)
{
for (int y = 0; y < 3; ++y)
{
/* Perform intermediate computation in double precision even if the
* result is in single-precision, to avoid unnecessary errors. */
double sum = 0;
for (int k = 0; k < 3; ++k)
sum += space_matrix[y * 3 + k] * range_matrix[k * 4 + x];
/* Notice the reversed indices: x is now the row, y is the
* column. */
conv_matrix_out[x * 4 + y] = sum;
}
}
/* Add a row to fill a 4x4 matrix (remember it's in column-major order).
* (non-square matrices are not supported on old OpenGL ES versions) */
conv_matrix_out[3] = 0;
conv_matrix_out[7] = 0;
conv_matrix_out[11] = 0;
conv_matrix_out[15] = 1;
}
static int
sampler_yuv_base_init(struct vlc_gl_sampler *sampler,
const vlc_chroma_description_t *desc,
video_color_space_t yuv_space)
{
struct sampler_priv *priv = PRIV(sampler);
/* The current implementation always converts from limited to full range. */
const video_color_range_t range = COLOR_RANGE_LIMITED;
float *matrix = priv->conv_matrix;
init_conv_matrix(matrix, yuv_space, range);
/* If using half_float, we multiply the conversion matrix by 255. Reason:
* half-float YUV planes store values in [0..1]. Multiplying by 255
* compensates for that.
*/
if (sampler->half_float)
for (int i = 0; i < 4*3; ++i)
matrix[i] *= 255;
if (desc->pixel_size == 2)
{
if (desc->fcc != VLC_CODEC_P010 && desc->fcc != VLC_CODEC_P012
&& desc->fcc != VLC_CODEC_P016) {
/* Do a bit shift if samples are stored on LSB. */
float yuv_range_correction = (float)((1 << 16) - 1)
/ ((1 << desc->pixel_bits) - 1);
/* We want to transform the input color (y, u, v, 1) to
* (r*y, r*u, r*v, 1), where r = yuv_range_correction.
*
* This can be done by left-multiplying the color vector by a
* matrix R:
*
* R
* / r*y \ / r 0 0 0 \ / y \
* | r*u | = | 0 r 0 0 | * | u |
* | r*v | | 0 0 r 0 | | v |
* \ 1 / \ 0 0 0 1 / \ 1 /
*
* Combine this transformation with the color conversion matrix:
*
* matrix := matrix * R
*
* This is equivalent to multipying the 3 first rows by r
* (yuv_range_conversion).
*/
for (int i = 0; i < 4*3; ++i)
matrix[i] *= yuv_range_correction;
}
}
priv->yuv_color = true;
/* Some formats require to swap the U and V components.
*
* This can be done by left-multiplying the color vector by a matrix S:
*
* S
* / y \ / 1 0 0 0 \ / y \
* | v | = | 0 0 1 0 | * | u |
* | u | | 0 1 0 0 | | v |
* \ 1 / \ 0 0 0 1 / \ 1 /
*
* Combine this transformation with the color conversion matrix:
*
* matrix := matrix * S
*
* This is equivalent to swap columns 1 and 2.
*/
bool swap_uv = desc->fcc == VLC_CODEC_YV12 ||
desc->fcc == VLC_CODEC_NV21;
if (swap_uv)
{
/* Remember, the matrix in column-major order */
float tmp[4];
/* tmp <- column1 */
memcpy(tmp, matrix + 4, sizeof(tmp));
/* column1 <- column2 */
memcpy(matrix + 4, matrix + 8, sizeof(tmp));
/* column2 <- tmp */
memcpy(matrix + 8, tmp, sizeof(tmp));
}
return VLC_SUCCESS;
}
static void
sampler_base_fetch_locations(struct vlc_gl_sampler *sampler, uint32_t program)
{
struct sampler_priv *priv = PRIV(sampler);
const opengl_vtable_t *vt = priv->vt;
if (priv->yuv_color)
{
priv->uloc.ConvMatrix = vt->GetUniformLocation(program, "ConvMatrix");
assert(priv->uloc.ConvMatrix != -1);
}
const unsigned tex_count = sampler->tex_count;
/* To guarantee variable names length, we need to fix the number
* of texture from now on.
* tex_count > PICTURE_PLANE_MAX (5) would overflow uloc.Textures
* tex_count > 9 would overflow the char name[] string during
* snprintf operations. */
if (tex_count >= PICTURE_PLANE_MAX)
vlc_assert_unreachable();
for (unsigned i = 0; i < tex_count; ++i)
{
char name[sizeof("Textures[X]")];
snprintf(name, sizeof(name), "Textures[%1u]", i);
priv->uloc.Textures[i] = vt->GetUniformLocation(program, name);
assert(priv->uloc.Textures[i] != -1);
if (sampler->tex_target == GL_TEXTURE_RECTANGLE)
{
snprintf(name, sizeof(name), "TexSizes[%1u]", i);
priv->uloc.TexSizes[i] = vt->GetUniformLocation(program, name);
assert(priv->uloc.TexSizes[i] != -1);
}
}
#ifdef HAVE_LIBPLACEBO_GL
const struct pl_shader_res *res = priv->pl_sh_res;
for (int i = 0; res && i < res->num_variables; i++) {
struct pl_shader_var sv = res->variables[i];
priv->uloc.pl_vars[i] = vt->GetUniformLocation(program, sv.var.name);
}
for (int i = 0; res && i < res->num_descriptors; i++) {
struct pl_shader_desc sd = res->descriptors[i];
priv->uloc.pl_descs[i] = vt->GetUniformLocation(program, sd.desc.name);
}
#endif
}
static void
sampler_base_load(struct vlc_gl_sampler *sampler)
{
struct sampler_priv *priv = PRIV(sampler);
const opengl_vtable_t *vt = priv->vt;
struct vlc_gl_picture *pic = &priv->pic;
if (priv->yuv_color)
vt->UniformMatrix4fv(priv->uloc.ConvMatrix, 1, GL_FALSE,
priv->conv_matrix);
for (unsigned i = 0; i < sampler->tex_count; ++i)
{
vt->Uniform1i(priv->uloc.Textures[i], i);
assert(pic->textures[i] != 0);
vt->ActiveTexture(GL_TEXTURE0 + i);
vt->BindTexture(sampler->tex_target, pic->textures[i]);
}
if (sampler->tex_target == GL_TEXTURE_RECTANGLE)
{
for (unsigned i = 0; i < sampler->tex_count; ++i)
vt->Uniform2f(priv->uloc.TexSizes[i], sampler->tex_widths[i],
sampler->tex_heights[i]);
}
#ifdef HAVE_LIBPLACEBO_GL
const struct pl_shader_res *res = priv->pl_sh_res;
for (int i = 0; res && i < res->num_variables; i++) {
GLint loc = priv->uloc.pl_vars[i];
if (loc == -1) // uniform optimized out
continue;
struct pl_shader_var sv = res->variables[i];
struct pl_var var = sv.var;
// libplacebo doesn't need anything else anyway
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, var.dim_a, GL_FALSE, f); break;
case 3: vt->UniformMatrix3fv(loc, var.dim_a, GL_FALSE, f); break;
case 2: vt->UniformMatrix2fv(loc, var.dim_a, GL_FALSE, f); break;
case 1:
switch (var.dim_v) {
case 1: vt->Uniform1fv(loc, var.dim_a, f); break;
case 2: vt->Uniform2fv(loc, var.dim_a, f); break;
case 3: vt->Uniform3fv(loc, var.dim_a, f); break;
case 4: vt->Uniform4fv(loc, var.dim_a, f); break;
}
break;
}
}
for (int i = 0; res && i < res->num_descriptors; i++) {
GLint loc = priv->uloc.pl_descs[i];
if (loc == -1)
continue;
struct pl_shader_desc sd = res->descriptors[i];
assert(sd.desc.type == PL_DESC_SAMPLED_TEX);
pl_tex tex = sd.binding.object;
int texid = sampler->tex_count + i; // first free texture unit
unsigned gltex, target;
gltex = pl_opengl_unwrap(priv->pl_opengl->gpu, tex, &target, NULL, NULL);
vt->Uniform1i(loc, texid);
vt->ActiveTexture(GL_TEXTURE0 + texid);
vt->BindTexture(target, gltex);
static const GLint wraps[PL_TEX_ADDRESS_MODE_COUNT] = {
[PL_TEX_ADDRESS_CLAMP] = GL_CLAMP_TO_EDGE,
[PL_TEX_ADDRESS_REPEAT] = GL_REPEAT,
[PL_TEX_ADDRESS_MIRROR] = GL_MIRRORED_REPEAT,
};
static const GLint filters[PL_TEX_SAMPLE_MODE_COUNT] = {
[PL_TEX_SAMPLE_NEAREST] = GL_NEAREST,
[PL_TEX_SAMPLE_LINEAR] = GL_LINEAR,
};
GLint filter = filters[sd.binding.sample_mode];
GLint wrap = wraps[sd.binding.address_mode];
vt->TexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
vt->TexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
switch (pl_tex_params_dimension(tex->params)) {
case 3: vt->TexParameteri(target, GL_TEXTURE_WRAP_R, wrap); // fall through
case 2: vt->TexParameteri(target, GL_TEXTURE_WRAP_T, wrap); // fall through
case 1: vt->TexParameteri(target, GL_TEXTURE_WRAP_S, wrap); break;
}
}
#endif
}
static void
sampler_xyz12_fetch_locations(struct vlc_gl_sampler *sampler, uint32_t program)
{
struct sampler_priv *priv = PRIV(sampler);
const opengl_vtable_t *vt = priv->vt;
priv->uloc.Textures[0] = vt->GetUniformLocation(program, "Textures[0]");
assert(priv->uloc.Textures[0] != -1);
}
static void
sampler_xyz12_load(struct vlc_gl_sampler *sampler)
{
struct sampler_priv *priv = PRIV(sampler);
const opengl_vtable_t *vt = priv->vt;
struct vlc_gl_picture *pic = &priv->pic;
vt->Uniform1i(priv->uloc.Textures[0], 0);
assert(pic->textures[0] != 0);
vt->ActiveTexture(GL_TEXTURE0);
vt->BindTexture(sampler->tex_target, pic->textures[0]);
}
static int
xyz12_shader_init(struct vlc_gl_sampler *sampler)
{
static const struct vlc_gl_sampler_ops ops = {
.fetch_locations = sampler_xyz12_fetch_locations,
.load = sampler_xyz12_load,
.update = sampler_update,
.select_plane = sampler_select_plane,
.close = sampler_close,
};
sampler->ops = &ops;
/* Shader for XYZ to RGB correction
* 3 steps :
* - XYZ gamma correction
* - XYZ to RGB matrix conversion
* - reverse RGB gamma correction
*/
static const char *template =
"uniform sampler2D Textures[1];"
"uniform vec4 xyz_gamma = vec4(2.6);"
"uniform vec4 rgb_gamma = vec4(1.0/2.2);"
/* WARN: matrix Is filled column by column (not row !) */
"uniform mat4 matrix_xyz_rgb = mat4("
" 3.240454 , -0.9692660, 0.0556434, 0.0,"
" -1.5371385, 1.8760108, -0.2040259, 0.0,"
" -0.4985314, 0.0415560, 1.0572252, 0.0,"
" 0.0, 0.0, 0.0, 1.0 "
" );"
"vec4 vlc_texture(vec2 tex_coords)\n"
"{ "
" vec4 v_in, v_out;"
" v_in = texture2D(Textures[0], tex_coords);\n"
" v_in = pow(v_in, xyz_gamma);"
" v_out = matrix_xyz_rgb * v_in ;"
" v_out = pow(v_out, rgb_gamma) ;"
" v_out = clamp(v_out, 0.0, 1.0) ;"
" return v_out;"
"}\n";
sampler->shader.body = strdup(template);
if (!sampler->shader.body)
return VLC_ENOMEM;
return VLC_SUCCESS;
}
static int
opengl_init_swizzle(struct vlc_gl_sampler *sampler,
const char *swizzle_per_tex[],
const vlc_chroma_description_t *desc)
{
if (desc->plane_count == 4)
swizzle_per_tex[0] = swizzle_per_tex[1] = swizzle_per_tex[2] = swizzle_per_tex[3] = "r";
else if (desc->plane_count == 3)
swizzle_per_tex[0] = swizzle_per_tex[1] = swizzle_per_tex[2] = "r";
else if (desc->plane_count == 2)
{
swizzle_per_tex[0] = "r";
if (sampler->formats[1] == GL_RG)
swizzle_per_tex[1] = "rg";
else
swizzle_per_tex[1] = "ra";
}
else if (desc->plane_count == 1)
{
/*
* One plane, but uploaded into two separate textures for Y and UV.
*
* Set swizzling in Y1 U V order
* R G B A
* U Y1 V Y2 => GRB
* Y1 U Y2 V => RGA
* V Y1 U Y2 => GBR
* Y1 V Y2 U => RAG
*/
switch (desc->fcc)
{
case VLC_CODEC_UYVY:
swizzle_per_tex[0] = "g";
swizzle_per_tex[1] = "rb";
break;
case VLC_CODEC_YUYV:
swizzle_per_tex[0] = "r";
swizzle_per_tex[1] = "ga";
break;
case VLC_CODEC_VYUY:
swizzle_per_tex[0] = "g";
swizzle_per_tex[1] = "br";
break;
case VLC_CODEC_YVYU:
swizzle_per_tex[0] = "r";
swizzle_per_tex[1] = "ag";
break;
case VLC_CODEC_V308:
swizzle_per_tex[0] = "rgb";
assert(sampler->tex_count == 1);
break;
case VLC_CODEC_VUYX:
case VLC_CODEC_VUYA:
swizzle_per_tex[0] = "bgr";
break;
case VLC_CODEC_Y410:
case VLC_CODEC_Y412:
swizzle_per_tex[0] = "grb";
break;
default:
assert(!"missing chroma");
return VLC_EGENERIC;
}
}
return VLC_SUCCESS;
}
static void
GetNames(struct vlc_gl_sampler *sampler, GLenum tex_target,
const char **glsl_sampler, const char **texture)
{
struct sampler_priv *priv = PRIV(sampler);
bool has_texture_func =
(priv->gl->api_type == VLC_OPENGL && priv->glsl_version >= 130) ||
(priv->gl->api_type == VLC_OPENGL_ES2 && priv->glsl_version >= 300);
//GLES has no texture rectangle
bool has_texture_rect_func =
(priv->gl->api_type == VLC_OPENGL && priv->glsl_version >= 140);
const bool is_yuv = vlc_fourcc_IsYUV(sampler->fmt_in.i_chroma);
switch (tex_target)
{
case GL_TEXTURE_EXTERNAL_OES:
*glsl_sampler = is_yuv ? "__samplerExternal2DY2YEXT" : "samplerExternalOES";
*texture = has_texture_func ? "texture" : "texture2D";
break;
case GL_TEXTURE_2D:
*glsl_sampler = priv->unsigned_sampler ? "usampler2D" : "sampler2D";
*texture = has_texture_func ? "texture" : "texture2D";
break;
case GL_TEXTURE_RECTANGLE:
*glsl_sampler = "sampler2DRect";
*texture = has_texture_rect_func ? "texture" : "texture2DRect";
break;
default:
vlc_assert_unreachable();
}
}
static int
InitShaderExtensions(struct vlc_gl_sampler *sampler, GLenum tex_target)
{
struct sampler_priv *priv = PRIV(sampler);
const bool is_yuv = vlc_fourcc_IsYUV(sampler->fmt_in.i_chroma);
const char *image_external = priv->glsl_version >= 300
? "#extension GL_OES_EGL_image_external_essl3 : require\n"
: "#extension GL_OES_EGL_image_external : require\n";
const char *ext_yuv_target =
"#extension GL_EXT_YUV_target : require\n";
const char *image_ext = "";
if (tex_target == GL_TEXTURE_EXTERNAL_OES)
{
if (is_yuv)
image_ext = ext_yuv_target;
else
image_ext = image_external;
}
const char *texture3D_ext = priv->api.supports_sampler3D
? "#extension GL_OES_texture_3D : enable\n" : "";
int ret = asprintf(&sampler->shader.extensions,"%s%s\n", image_ext, texture3D_ext);
if (ret <= 0)
{
if (ret == 0)
FREENULL(sampler->shader.extensions);
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
static void
sampler_planes_fetch_locations(struct vlc_gl_sampler *sampler, uint32_t program)
{
struct sampler_priv *priv = PRIV(sampler);
const opengl_vtable_t *vt = priv->vt;
priv->uloc.Textures[0] = vt->GetUniformLocation(program, "Texture");
assert(priv->uloc.Textures[0] != -1);
if (sampler->tex_target == GL_TEXTURE_RECTANGLE)
{
priv->uloc.TexSizes[0] = vt->GetUniformLocation(program, "TexSize");
assert(priv->uloc.TexSizes[0] != -1);
}
}
static void
sampler_planes_load(struct vlc_gl_sampler *sampler)
{
struct sampler_priv *priv = PRIV(sampler);
unsigned plane = priv->plane;
const opengl_vtable_t *vt = priv->vt;
struct vlc_gl_picture *pic = &priv->pic;
vt->Uniform1i(priv->uloc.Textures[0], 0);
assert(pic->textures[plane] != 0);
vt->ActiveTexture(GL_TEXTURE0);
vt->BindTexture(sampler->tex_target, pic->textures[plane]);
if (sampler->tex_target == GL_TEXTURE_RECTANGLE)
{
vt->Uniform2f(priv->uloc.TexSizes[0], sampler->tex_widths[plane],
sampler->tex_heights[plane]);
}
}
static int
sampler_planes_init(struct vlc_gl_sampler *sampler)
{
GLenum tex_target = sampler->tex_target;
struct vlc_memstream ms;
if (vlc_memstream_open(&ms))
return VLC_EGENERIC;
#define ADD(x) vlc_memstream_puts(&ms, x)
#define ADDF(x, ...) vlc_memstream_printf(&ms, x, ##__VA_ARGS__)
const char *sampler_type;
const char *texture_fn;
GetNames(sampler, tex_target, &sampler_type, &texture_fn);
ADDF("uniform %s Texture;\n", sampler_type);
if (tex_target == GL_TEXTURE_RECTANGLE)
ADD("uniform vec2 TexSize;\n");
ADD("vec4 vlc_texture(vec2 tex_coords) {\n");
if (tex_target == GL_TEXTURE_RECTANGLE)
{
/* The coordinates are in texels values, not normalized */
ADD(" tex_coords = TexSize * tex_coords;\n");
}
ADDF(" return %s(Texture, tex_coords);\n", texture_fn);
ADD("}\n");
#undef ADD
#undef ADDF
if (vlc_memstream_close(&ms) != 0)
return VLC_EGENERIC;
int ret = InitShaderExtensions(sampler, tex_target);
if (ret != VLC_SUCCESS)
{
free(ms.ptr);
return VLC_EGENERIC;
}
sampler->shader.body = ms.ptr;
static const struct vlc_gl_sampler_ops ops = {
.fetch_locations = sampler_planes_fetch_locations,
.load = sampler_planes_load,
.update = sampler_update,
.select_plane = sampler_select_plane,
.close = sampler_close,
};
sampler->ops = &ops;
return VLC_SUCCESS;
}
#ifdef HAVE_LIBPLACEBO_GL
static struct pl_custom_lut *LoadCustomLUT(struct vlc_gl_sampler *sampler,
const char *filepath)
{
struct sampler_priv *priv = PRIV(sampler);
if (!filepath || !filepath[0])
return NULL;
FILE *fs = vlc_fopen(filepath, "rb");
struct pl_custom_lut *lut = NULL;
char *lut_file = NULL;
if (!fs)
goto error;
int ret = fseek(fs, 0, SEEK_END);
if (ret == -1)
goto error;
long length = ftell(fs);
if (length < 0)
goto error;
rewind(fs);
lut_file = vlc_alloc(length, sizeof(*lut_file));
if (!lut_file)
goto error;
ret = fread(lut_file, length, 1, fs);
if (ret != 1)
goto error;
lut = pl_lut_parse_cube(priv->pl_log, lut_file, length);
// fall through
error:
if (fs)
fclose(fs);
free(lut_file);
return lut;
}
#endif
static int
opengl_fragment_shader_init(struct vlc_gl_sampler *sampler, bool expose_planes)
{
struct sampler_priv *priv = PRIV(sampler);
const video_format_t *fmt = &sampler->fmt_in;
GLenum tex_target = sampler->tex_target;
priv->expose_planes = expose_planes;
priv->plane = 0;
vlc_fourcc_t chroma = fmt->i_chroma;
video_color_space_t yuv_space = fmt->space;
const char *swizzle_per_tex[PICTURE_PLANE_MAX] = { NULL, };
const bool is_yuv = vlc_fourcc_IsYUV(chroma);
int ret;
const vlc_chroma_description_t *desc = vlc_fourcc_GetChromaDescription(chroma);
if (desc == NULL)
{
msg_Dbg(priv->gl, "opengl sampler: unknown chroma description %4.4s", (const char *)&chroma);
return VLC_EGENERIC;
}
unsigned tex_count = sampler->tex_count;
if (expose_planes)
return sampler_planes_init(sampler);
if (chroma == VLC_CODEC_XYZ_12L ||
chroma == VLC_CODEC_XYZ_12B)
return xyz12_shader_init(sampler);
const bool is_y21x_packed = chroma == VLC_CODEC_Y210
|| chroma == VLC_CODEC_Y212;
if (is_yuv)
{
ret = sampler_yuv_base_init(sampler, desc, yuv_space);
if (ret != VLC_SUCCESS)
{
msg_Dbg(priv->gl, "opengl sampler: could not initialize YUV shaders for %4.4s", (const char *)&chroma);
return ret;
}
if (!is_y21x_packed)
{
ret = opengl_init_swizzle(sampler, swizzle_per_tex, desc);
if (ret != VLC_SUCCESS)
{
msg_Dbg(priv->gl, "opengl sampler: could not determine swizzle for %4.4s", (const char *)&chroma);
return ret;
}
}
}
const char *glsl_sampler, *lookup;
GetNames(sampler, tex_target, &glsl_sampler, &lookup);
struct vlc_memstream ms;
if (vlc_memstream_open(&ms) != 0)
return VLC_EGENERIC;
#define ADD(x) vlc_memstream_puts(&ms, x)
#define ADDF(x, ...) vlc_memstream_printf(&ms, x, ##__VA_ARGS__)
ADDF("uniform %s Textures[%u];\n", glsl_sampler, tex_count);
#ifdef HAVE_LIBPLACEBO_GL
if (priv->pl_sh) {
pl_shader sh = priv->pl_sh;
struct pl_color_map_params color_params;
vlc_placebo_ColorMapParams(VLC_OBJECT(priv->gl), "gl", &color_params);
struct pl_color_space src_space = vlc_placebo_ColorSpace(fmt);
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");
char *lut_file = var_InheritString(priv->gl, "gl-lut-file");
struct pl_custom_lut *lut = LoadCustomLUT(sampler, lut_file);
free(lut_file);
if (lut) {
// Transform from the video input to the LUT input color space,
// defaulting to a no-op if LUT input color space info is unknown
dst_space = lut->color_in;
pl_color_space_merge(&dst_space, &src_space);
}
pl_shader_color_map(sh, &color_params, src_space, dst_space,
&priv->tone_map_state, false);
if (lut) {
pl_shader_custom_lut(sh, lut, &priv->lut_state);
pl_lut_free(&lut);
}
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)
const opengl_vtable_t *vt = priv->vt;
/* fetch framebuffer depth (we are already bound to the default one). */
if (priv->gl->api_type == VLC_OPENGL &&
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(sh, out_bits, &priv->dither_state, &(struct pl_dither_params) {
.method = method,
});
}
const struct pl_shader_res *res = priv->pl_sh_res = pl_shader_finalize(sh);
# if PL_API_VER >= 266
msg_Dbg(priv->gl, "libplacebo shader: %s", res->info->description);
# else
msg_Dbg(priv->gl, "libplacebo shader: %s", res->description);
# endif
FREENULL(priv->uloc.pl_vars);
priv->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];
const char *glsl_type_name = pl_var_glsl_type_name(sv.var);
ADDF("uniform %s %s", glsl_type_name, sv.var.name);
if (sv.var.dim_a > 1) {
ADDF("[%d];\n", sv.var.dim_a);
} else {
ADDF(";\n");
}
}
FREENULL(priv->uloc.pl_descs);
priv->uloc.pl_descs = calloc(res->num_descriptors, sizeof(GLint));
for (int i = 0; i < res->num_descriptors; i++) {
struct pl_shader_desc sd = res->descriptors[i];
assert(sd.desc.type == PL_DESC_SAMPLED_TEX);
pl_tex tex = sd.binding.object;
assert(tex->sampler_type == PL_SAMPLER_NORMAL);
int dims = pl_tex_params_dimension(tex->params);
ADDF("uniform sampler%dD %s;\n", dims, sd.desc.name);
}
// We can't handle these yet, but nothing we use requires them, either
assert(res->num_vertex_attribs == 0);
ADD(res->glsl);
}
#else
if (fmt->transfer == TRANSFER_FUNC_SMPTE_ST2084 ||
fmt->primaries == COLOR_PRIMARIES_BT2020)
{
// no warning for HLG because it's more or less backwards-compatible
msg_Warn(priv->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)
ADDF("uniform vec2 TexSizes[%u];\n", tex_count);
if (is_yuv)
ADD("uniform mat4 ConvMatrix;\n");
ADD("vec4 vlc_texture(vec2 tex_coords) {\n");
unsigned color_count;
if (is_y21x_packed)
{
assert(is_yuv);
assert(tex_count == 1);
/* Y210/Y212: packed 4:2:2, each 8-byte texel holds 2 pixels:
* [Y0:16][U:16][Y1:16][V:16] -> sampled as (R=Y0, G=U, B=Y1, A=V)
* A simple swizzle can't select the correct luma, so bypass the
* generic path and use texelFetch to pick Y0 or Y1 by pixel parity. */
ADD(" ivec2 _ts = textureSize(Textures[0], 0);\n"
" int _px = int(tex_coords.x * float(_ts.x) * 2.0);\n"
" int _py = int(tex_coords.y * float(_ts.y));\n"
" vec4 _t = texelFetch(Textures[0], ivec2(_px >> 1, _py), 0);\n"
" float _Y = ((_px & 1) == 0) ? _t.r : _t.b;\n"
" vec4 pixel = vec4(_Y, _t.g, _t.a, 1.0);\n"
" vec4 result = ConvMatrix * pixel;\n");
color_count = 3;
}
else if (is_yuv)
{
if (priv->unsigned_sampler)
{
/* One extra integer -> float step will be needed */
for (unsigned i = 0; i < tex_count; ++i)
{
if (tex_target == GL_TEXTURE_RECTANGLE)
ADDF(" uvec4 t%u = %s(Textures[%u], TexSizes[%u] * tex_coords);\n",
i, lookup, i, i);
else
ADDF(" uvec4 t%u = %s(Textures[%u], tex_coords);\n",
i, lookup, i);
}
}
ADD(" vec4 pixel = vec4(\n");
color_count = 0;
for (unsigned i = 0; i < tex_count; ++i)
{
const char *swizzle = swizzle_per_tex[i];
assert(swizzle);
color_count += strlen(swizzle);
assert(color_count < PICTURE_PLANE_MAX);
if (i > 0)
ADD(" ,");
if (priv->unsigned_sampler)
{
/* Integer -> float */
ADDF(" float(t%u.%s) / 65535.0\n", i, swizzle);
}
else if (tex_target == GL_TEXTURE_RECTANGLE)
{
/* The coordinates are in texels values, not normalized */
ADDF(" %s(Textures[%u], TexSizes[%u] * tex_coords).%s\n", lookup, i, i, swizzle);
}
else
{
ADDF(" %s(Textures[%u], tex_coords).%s\n", lookup, i, swizzle);
}
}
if (color_count == 3)
ADD(" , 1.0);\n");
else
ADD(");\n");
ADD(" vec4 result = ConvMatrix * pixel;\n");
}
else
{
if (tex_target == GL_TEXTURE_RECTANGLE)
ADD(" tex_coords *= TexSizes[0];\n");
ADDF(" vec4 result = %s(Textures[0], tex_coords);\n", lookup);
color_count = 1;
}
assert(yuv_space == COLOR_SPACE_UNDEF || color_count == 3 || color_count == 4);
#ifdef HAVE_LIBPLACEBO_GL
if (priv->pl_sh_res) {
const struct pl_shader_res *res = priv->pl_sh_res;
if (res->input != PL_SHADER_SIG_NONE) {
assert(res->input == PL_SHADER_SIG_COLOR);
assert(res->output == PL_SHADER_SIG_COLOR);
ADDF(" result = %s(result);\n", res->name);
}
}
#endif
ADD(" return result;\n"
"}\n");
#undef ADD
#undef ADDF
if (vlc_memstream_close(&ms) != 0)
return VLC_EGENERIC;
ret = InitShaderExtensions(sampler, tex_target);
if (ret != VLC_SUCCESS)
{
free(ms.ptr);
return VLC_EGENERIC;
}
sampler->shader.body = ms.ptr;
static const struct vlc_gl_sampler_ops ops = {
.fetch_locations = sampler_base_fetch_locations,
.load = sampler_base_load,
.update = sampler_update,
.select_plane = sampler_select_plane,
.close = sampler_close,
};
sampler->ops = &ops;
return VLC_SUCCESS;
}
#if defined(HAVE_LIBPLACEBO_GL) && PL_API_VER >= 215
static pl_voidfunc_t PlaceboGetProcAddr(void *opaque, const char *name)
{
vlc_gl_t *gl = opaque;
return (pl_voidfunc_t)vlc_gl_GetProcAddress(gl, name);
}
#endif
static int
Open(struct vlc_gl_sampler *sampler,
bool expose_planes)
{
struct sampler_priv *priv = calloc(1, sizeof(*priv));
if (!priv)
return VLC_ENOMEM;
sampler->sys = priv;
priv->gl = sampler->gl;
int ret = vlc_gl_api_Init(&priv->api, sampler->gl);
if (ret != VLC_SUCCESS)
{
free(priv);
return ret;
}
priv->vt = &priv->api.vt;
vlc_gl_LoadExtensionFunctions(sampler->gl, &priv->extension_vt);
memcpy(priv->pic.mtx, MATRIX2x3_IDENTITY, sizeof(MATRIX2x3_IDENTITY));
priv->pic.mtx_has_changed = true;
sampler->pic_to_tex_matrix = priv->pic.mtx;
sampler->shader.extensions = NULL;
sampler->shader.body = NULL;
switch (sampler->formats[0])
{
case GL_RED_INTEGER:
case GL_RG_INTEGER:
priv->unsigned_sampler = true;
break;
default:
priv->unsigned_sampler = false;
break;
}
const struct vlc_gl_api *api = &priv->api;
int glsl_version;
if (api->is_gles) {
if (asprintf(&sampler->shader.precision,
"precision highp float;\n"
"precision highp sampler2D;\n"
"%s%s",
api->supports_sampler3D ? "precision highp sampler3D;\n" : "",
priv->unsigned_sampler ? "precision highp usampler2D;\n" : "" ) <= 0 )
{
free(priv);
return VLC_ENOMEM;
}
if (api->glsl_version >= 300) {
sampler->shader.version = strdup("#version 300 es\n");
glsl_version = 300;
} else {
sampler->shader.version = strdup("#version 100\n");
glsl_version = 100;
}
if (!sampler->shader.version)
{
free(sampler->shader.precision);
sampler->shader.precision = NULL;
free(priv);
return VLC_ENOMEM;
}
} else {
sampler->shader.precision = strdup("// no precision\n");
if (!sampler->shader.precision)
{
free(priv);
return VLC_ENOMEM;
}
/* GLSL version 420+ breaks backwards compatibility with pre-GLSL 130
* syntax, which we use in our vertex/fragment shaders. */
glsl_version = __MIN(api->glsl_version, 410);
if (asprintf(&sampler->shader.version, "#version %d\n", glsl_version) < 0)
{
free(sampler->shader.precision);
sampler->shader.precision = NULL;
free(priv);
return VLC_ENOMEM;
}
}
priv->glsl_version = glsl_version;
#ifdef HAVE_LIBPLACEBO_GL
priv->uloc.pl_vars = NULL;
priv->uloc.pl_descs = NULL;
priv->pl_sh_res = NULL;
priv->pl_log = vlc_placebo_CreateLog(VLC_OBJECT(sampler->gl));
const struct pl_opengl_params gl_params =
{
/* Libplacebo won't control whether the context is current or not.
* This is the default but we also want to ensure that the struct is
* properly initialized even if PL_API_VER < 215. */
.make_current = NULL,
.release_current = NULL,
#if PL_API_VER >= 215
.get_proc_addr_ex = PlaceboGetProcAddr,
.proc_ctx = sampler->gl,
#endif
};
const opengl_vtable_t *vt = priv->vt;
/* Workaround libplacebo changing the current framebuffer when running
* with OpenGL. */
GLint out_fb;
vt->GetIntegerv(GL_FRAMEBUFFER_BINDING, &out_fb);
priv->pl_opengl = pl_opengl_create(priv->pl_log, &gl_params);
if (!priv->pl_opengl)
{
msg_Dbg(sampler->gl, "opengl sampler: could not initialize libplacebo");
pl_log_destroy(&priv->pl_log);
free(sampler->shader.precision);
sampler->shader.precision = NULL;
free(sampler->shader.version);
sampler->shader.version = NULL;
free(priv);
return VLC_EGENERIC;
}
vt->BindFramebuffer(GL_FRAMEBUFFER, out_fb);
priv->pl_sh = pl_shader_alloc(priv->pl_log, &(struct pl_shader_params) {
.gpu = priv->pl_opengl->gpu,
.glsl = {
.version = glsl_version,
.gles = sampler->gl->api_type == VLC_OPENGL_ES2,
},
});
#endif
ret = opengl_fragment_shader_init(sampler, expose_planes);
if (ret != VLC_SUCCESS)
{
msg_Dbg(sampler->gl, "opengl sampler: could not initialize shaders");
free(sampler->shader.precision);
sampler->shader.precision = NULL;
free(sampler->shader.version);
sampler->shader.version = NULL;
free(priv);
return ret;
}
return VLC_SUCCESS;
}
#include <vlc_plugin.h>
vlc_module_begin()
set_shortname("glsampler")
set_description("OpenGL built-in sampler")
set_callback_opengl_sampler(Open)
set_capability("opengl sampler", 1)
set_subcategory(SUBCAT_VIDEO_VOUT)
#ifdef HAVE_LIBPLACEBO_GL
set_section(N_("Colorspace conversion"), NULL)
add_placebo_color_map_opts("gl")
add_integer("target-prim", PL_COLOR_PRIM_UNKNOWN,
N_("Target primaries"), N_("Override the target color primaries"))
change_integer_list(prim_values, prim_text)
add_integer("target-trc", PL_COLOR_TRC_UNKNOWN,
N_("Target transfer"), N_("Override the target transfer function"))
change_integer_list(trc_values, trc_text)
set_section(N_("Dithering"), NULL)
add_integer("dither-algo", -1,
N_("Dithering algorithm"), N_("Algorithm for dithering"))
change_integer_list(dither_values, dither_text)
add_integer_with_range("dither-depth", 0, 0, 16,
N_("Dither depth"), N_("Override the dither depth"))
add_loadfile("gl-lut-file", NULL,
N_("Custom LUT"), N_("Path to a custom .cube LUT file"))
#endif
vlc_module_end()
¤ Dauer der Verarbeitung: 0.25 Sekunden
¤
*© Formatika GbR, Deutschland
|
|