1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-29 18:42:09 +00:00
mpv/libvo/vo_opengl.c
2012-10-23 00:08:59 +02:00

2608 lines
83 KiB
C

/*
* This file is part of MPlayer.
*
* MPlayer is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* MPlayer 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* You can alternatively redistribute this file 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include <assert.h>
#include "config.h"
#include <libavutil/common.h>
#ifdef CONFIG_LCMS2
#include <lcms2.h>
#include "stream/stream.h"
#endif
#include "talloc.h"
#include "bstr.h"
#include "mp_msg.h"
#include "subopt-helper.h"
#include "video_out.h"
#include "libmpcodecs/vfcap.h"
#include "libmpcodecs/mp_image.h"
#include "geometry.h"
#include "osd.h"
#include "sub/sub.h"
#include "eosd_packer.h"
#include "gl_common.h"
#include "filter_kernels.h"
#include "aspect.h"
#include "fastmemcpy.h"
#include "sub/ass_mp.h"
static const char vo_opengl_shaders[] =
// Generated from libvo/vo_opengl_shaders.glsl
#include "libvo/vo_opengl_shaders.h"
;
// How many parts the OSD may consist of at most.
#define MAX_OSD_PARTS 20
// Pixel width of 1D lookup textures.
#define LOOKUP_TEXTURE_SIZE 256
// Texture units 0-2 are used by the video, with unit 0 for free use.
// Units 3-4 are used for scaler LUTs.
#define TEXUNIT_SCALERS 3
#define TEXUNIT_3DLUT 5
#define TEXUNIT_DITHER 6
// lscale/cscale arguments that map directly to shader filter routines.
// Note that the convolution filters are not included in this list.
static const char *fixed_scale_filters[] = {
"bilinear",
"bicubic_fast",
"sharpen3",
"sharpen5",
NULL
};
struct lut_tex_format {
int pixels;
GLint internal_format;
GLenum format;
};
// Indexed with filter_kernel->size.
// This must match the weightsN functions in the shader.
// Each entry uses (size+3)/4 pixels per LUT entry, and size/pixels components
// per pixel.
struct lut_tex_format lut_tex_formats[] = {
[2] = {1, GL_RG16F, GL_RG},
[4] = {1, GL_RGBA16F, GL_RGBA},
[6] = {2, GL_RGB16F, GL_RGB},
[8] = {2, GL_RGBA16F, GL_RGBA},
[12] = {3, GL_RGBA16F, GL_RGBA},
[16] = {4, GL_RGBA16F, GL_RGBA},
};
// must be sorted, and terminated with 0
static const int filter_sizes[] = {2, 4, 6, 8, 12, 16, 0};
struct vertex {
float position[2];
uint8_t color[4];
float texcoord[2];
};
#define VERTEX_ATTRIB_POSITION 0
#define VERTEX_ATTRIB_COLOR 1
#define VERTEX_ATTRIB_TEXCOORD 2
// 2 triangles primitives per quad = 6 vertices per quad
// (GL_QUAD is deprecated, strips can't be used with EOSD image lists)
#define VERTICES_PER_QUAD 6
struct texplane {
int shift_x, shift_y;
GLuint gl_texture;
int gl_buffer;
int buffer_size;
void *buffer_ptr;
};
struct scaler {
int index;
const char *name;
float params[2];
struct filter_kernel *kernel;
GLuint gl_lut;
const char *lut_name;
// kernel points here
struct filter_kernel kernel_storage;
};
struct fbotex {
GLuint fbo;
GLuint texture;
int tex_w, tex_h; // size of .texture
int vp_w, vp_h; // viewport of fbo / used part of the texture
};
struct gl_priv {
struct vo *vo;
MPGLContext *glctx;
GL *gl;
int use_indirect;
int use_gamma;
int use_srgb;
int use_scale_sep;
int use_fancy_downscaling;
int use_lut_3d;
int use_npot;
int use_pbo;
int use_glFinish;
int use_gl_debug;
int allow_sw;
int dither_depth;
int swap_interval;
GLint fbo_format;
int stereo_mode;
int osd_color;
struct gl_priv *defaults;
struct gl_priv *orig_cmdline;
GLuint vertex_buffer;
GLuint vao;
GLuint osd_program, eosd_program;
GLuint indirect_program, scale_sep_program, final_program;
GLuint osd_textures[MAX_OSD_PARTS];
int osd_textures_count;
struct vertex osd_va[MAX_OSD_PARTS * VERTICES_PER_QUAD];
GLuint eosd_texture;
int eosd_texture_width, eosd_texture_height;
GLuint eosd_buffer;
struct vertex *eosd_va;
struct eosd_packer *eosd;
GLuint lut_3d_texture;
int lut_3d_w, lut_3d_h, lut_3d_d;
void *lut_3d_data;
GLuint dither_texture;
float dither_quantization;
float dither_multiply;
int dither_size;
uint32_t image_width;
uint32_t image_height;
uint32_t image_format;
int texture_width;
int texture_height;
bool is_yuv;
bool is_linear_rgb;
// per pixel (full pixel when packed, each component when planar)
int plane_bytes;
int plane_bits;
int component_bits;
GLint gl_internal_format;
GLenum gl_format;
GLenum gl_type;
int plane_count;
struct texplane planes[3];
struct fbotex indirect_fbo; // RGB target
struct fbotex scale_sep_fbo; // first pass when doing 2 pass scaling
// state for luma (0) and chroma (1) scalers
struct scaler scalers[2];
// luma scaler parameters (the same are used for chroma)
float scaler_params[2];
struct mp_csp_details colorspace;
struct mp_csp_equalizer video_eq;
int mpi_flipped;
int vo_flipped;
struct vo_rect src_rect; // displayed part of the source video
struct vo_rect dst_rect; // video rectangle on output window
int border_x, border_y; // OSD borders
int vp_x, vp_y, vp_w, vp_h; // GL viewport
int frames_rendered;
};
struct fmt_entry {
int mp_format;
GLint internal_format;
GLenum format;
int component_bits;
GLenum type;
};
static const struct fmt_entry mp_to_gl_formats[] = {
{IMGFMT_RGB48NE, GL_RGB16, GL_RGB, 16, GL_UNSIGNED_SHORT},
{IMGFMT_RGB24, GL_RGB, GL_RGB, 8, GL_UNSIGNED_BYTE},
{IMGFMT_RGBA, GL_RGBA, GL_RGBA, 8, GL_UNSIGNED_BYTE},
{IMGFMT_RGB15, GL_RGBA, GL_RGBA, 5, GL_UNSIGNED_SHORT_1_5_5_5_REV},
{IMGFMT_RGB16, GL_RGB, GL_RGB, 6, GL_UNSIGNED_SHORT_5_6_5_REV},
{IMGFMT_BGR15, GL_RGBA, GL_BGRA, 5, GL_UNSIGNED_SHORT_1_5_5_5_REV},
{IMGFMT_BGR16, GL_RGB, GL_RGB, 6, GL_UNSIGNED_SHORT_5_6_5},
{IMGFMT_BGR24, GL_RGB, GL_BGR, 8, GL_UNSIGNED_BYTE},
{IMGFMT_BGRA, GL_RGBA, GL_BGRA, 8, GL_UNSIGNED_BYTE},
{0},
};
static const char help_text[];
static void uninit_rendering(struct gl_priv *p);
static void delete_shaders(struct gl_priv *p);
static bool reparse_cmdline(struct gl_priv *p, char *arg);
static void default_tex_params(struct GL *gl, GLenum target, GLint filter)
{
gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
gl->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
static void debug_check_gl(struct gl_priv *p, const char *msg)
{
if (p->use_gl_debug || p->frames_rendered < 5)
glCheckError(p->gl, msg);
}
static void tex_size(struct gl_priv *p, int w, int h, int *texw, int *texh)
{
if (p->use_npot) {
*texw = w;
*texh = h;
} else {
*texw = 32;
while (*texw < w)
*texw *= 2;
*texh = 32;
while (*texh < h)
*texh *= 2;
}
}
static void draw_triangles(struct gl_priv *p, struct vertex *vb, int vert_count)
{
GL *gl = p->gl;
assert(vert_count % 3 == 0);
gl->BindBuffer(GL_ARRAY_BUFFER, p->vertex_buffer);
gl->BufferData(GL_ARRAY_BUFFER, vert_count * sizeof(struct vertex), vb,
GL_DYNAMIC_DRAW);
gl->BindBuffer(GL_ARRAY_BUFFER, 0);
if (gl->BindVertexArray)
gl->BindVertexArray(p->vao);
gl->DrawArrays(GL_TRIANGLES, 0, vert_count);
if (gl->BindVertexArray)
gl->BindVertexArray(0);
debug_check_gl(p, "after rendering");
}
// Write a textured quad to a vertex array.
// va = destination vertex array, VERTICES_PER_QUAD entries will be overwritten
// x0, y0, x1, y1 = destination coordinates of the quad
// tx0, ty0, tx1, ty1 = source texture coordinates (usually in pixels)
// texture_w, texture_h = size of the texture, or an inverse factor
// color = optional color for all vertices, NULL for opaque white
// flip = flip vertically
static void write_quad(struct vertex *va,
float x0, float y0, float x1, float y1,
float tx0, float ty0, float tx1, float ty1,
float texture_w, float texture_h,
const uint8_t color[4], bool flip)
{
static const uint8_t white[4] = { 255, 255, 255, 255 };
if (!color)
color = white;
tx0 /= texture_w;
ty0 /= texture_h;
tx1 /= texture_w;
ty1 /= texture_h;
if (flip) {
float tmp = ty0;
ty0 = ty1;
ty1 = tmp;
}
#define COLOR_INIT {color[0], color[1], color[2], color[3]}
va[0] = (struct vertex) { {x0, y0}, COLOR_INIT, {tx0, ty0} };
va[1] = (struct vertex) { {x0, y1}, COLOR_INIT, {tx0, ty1} };
va[2] = (struct vertex) { {x1, y0}, COLOR_INIT, {tx1, ty0} };
va[3] = (struct vertex) { {x1, y1}, COLOR_INIT, {tx1, ty1} };
va[4] = va[2];
va[5] = va[1];
#undef COLOR_INIT
}
static bool fbotex_init(struct gl_priv *p, struct fbotex *fbo, int w, int h)
{
GL *gl = p->gl;
bool res = true;
assert(gl->mpgl_caps & MPGL_CAP_FB);
assert(!fbo->fbo);
assert(!fbo->texture);
tex_size(p, w, h, &fbo->tex_w, &fbo->tex_h);
fbo->vp_w = w;
fbo->vp_h = h;
mp_msg(MSGT_VO, MSGL_V, "[gl] Create FBO: %dx%d\n", fbo->tex_w, fbo->tex_h);
gl->GenFramebuffers(1, &fbo->fbo);
gl->GenTextures(1, &fbo->texture);
gl->BindTexture(GL_TEXTURE_2D, fbo->texture);
gl->TexImage2D(GL_TEXTURE_2D, 0, p->fbo_format, fbo->tex_w, fbo->tex_h, 0,
GL_RGB, GL_UNSIGNED_BYTE, NULL);
default_tex_params(gl, GL_TEXTURE_2D, GL_LINEAR);
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, fbo->texture, 0);
if (gl->CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
mp_msg(MSGT_VO, MSGL_ERR, "[gl] Error: framebuffer completeness "
"check failed!\n");
res = false;
}
gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
debug_check_gl(p, "after creating framebuffer & associated texture");
return res;
}
static void fbotex_uninit(struct gl_priv *p, struct fbotex *fbo)
{
GL *gl = p->gl;
if (gl->mpgl_caps & MPGL_CAP_FB) {
gl->DeleteFramebuffers(1, &fbo->fbo);
gl->DeleteTextures(1, &fbo->texture);
*fbo = (struct fbotex) {0};
}
}
static void matrix_ortho2d(float m[3][3], float x0, float x1,
float y0, float y1)
{
memset(m, 0, 9 * sizeof(float));
m[0][0] = 2.0f / (x1 - x0);
m[1][1] = 2.0f / (y1 - y0);
m[2][0] = -(x1 + x0) / (x1 - x0);
m[2][1] = -(y1 + y0) / (y1 - y0);
m[2][2] = 1.0f;
}
static void update_uniforms(struct gl_priv *p, GLuint program)
{
GL *gl = p->gl;
GLint loc;
if (program == 0)
return;
gl->UseProgram(program);
struct mp_csp_params cparams = {
.colorspace = p->colorspace,
.input_bits = p->plane_bits,
.texture_bits = (p->plane_bits + 7) & ~7,
};
mp_csp_copy_equalizer_values(&cparams, &p->video_eq);
loc = gl->GetUniformLocation(program, "transform");
if (loc >= 0) {
float matrix[3][3];
matrix_ortho2d(matrix, 0, p->vp_w, p->vp_h, 0);
gl->UniformMatrix3fv(loc, 1, GL_FALSE, &matrix[0][0]);
}
loc = gl->GetUniformLocation(program, "colormatrix");
if (loc >= 0) {
float yuv2rgb[3][4] = {{0}};
if (p->is_yuv)
mp_get_yuv2rgb_coeffs(&cparams, yuv2rgb);
gl->UniformMatrix4x3fv(loc, 1, GL_TRUE, &yuv2rgb[0][0]);
}
gl->Uniform3f(gl->GetUniformLocation(program, "inv_gamma"),
1.0 / cparams.rgamma,
1.0 / cparams.ggamma,
1.0 / cparams.bgamma);
for (int n = 0; n < p->plane_count; n++) {
char textures_n[32];
char textures_size_n[32];
snprintf(textures_n, sizeof(textures_n), "textures[%d]", n);
snprintf(textures_size_n, sizeof(textures_size_n), "textures_size[%d]", n);
gl->Uniform1i(gl->GetUniformLocation(program, textures_n), n);
gl->Uniform2f(gl->GetUniformLocation(program, textures_size_n),
p->texture_width >> p->planes[n].shift_x,
p->texture_height >> p->planes[n].shift_y);
}
gl->Uniform2f(gl->GetUniformLocation(program, "dither_size"),
p->dither_size, p->dither_size);
gl->Uniform1i(gl->GetUniformLocation(program, "lut_3d"), TEXUNIT_3DLUT);
for (int n = 0; n < 2; n++) {
const char *lut = p->scalers[n].lut_name;
if (lut)
gl->Uniform1i(gl->GetUniformLocation(program, lut),
TEXUNIT_SCALERS + n);
}
gl->Uniform1i(gl->GetUniformLocation(program, "dither"), TEXUNIT_DITHER);
gl->Uniform1f(gl->GetUniformLocation(program, "dither_quantization"),
p->dither_quantization);
gl->Uniform1f(gl->GetUniformLocation(program, "dither_multiply"),
p->dither_multiply);
float sparam1 = p->scaler_params[0];
gl->Uniform1f(gl->GetUniformLocation(program, "filter_param1"),
isnan(sparam1) ? 0.5f : sparam1);
gl->UseProgram(0);
debug_check_gl(p, "update_uniforms()");
}
static void update_all_uniforms(struct gl_priv *p)
{
update_uniforms(p, p->osd_program);
update_uniforms(p, p->eosd_program);
update_uniforms(p, p->indirect_program);
update_uniforms(p, p->scale_sep_program);
update_uniforms(p, p->final_program);
}
#define SECTION_HEADER "#!section "
static char *get_section(void *talloc_ctx, struct bstr source,
const char *section)
{
char *res = talloc_strdup(talloc_ctx, "");
bool copy = false;
while (source.len) {
struct bstr line = bstr_strip_linebreaks(bstr_getline(source, &source));
if (bstr_eatstart(&line, bstr0(SECTION_HEADER))) {
copy = bstrcmp0(line, section) == 0;
} else if (copy) {
res = talloc_asprintf_append_buffer(res, "%.*s\n", BSTR_P(line));
}
}
return res;
}
static char *t_concat(void *talloc_ctx, const char *s1, const char *s2)
{
return talloc_asprintf(talloc_ctx, "%s%s", s1, s2);
}
static GLuint create_shader(GL *gl, GLenum type, const char *header,
const char *source)
{
void *tmp = talloc_new(NULL);
const char *full_source = t_concat(tmp, header, source);
GLuint shader = gl->CreateShader(type);
gl->ShaderSource(shader, 1, &full_source, NULL);
gl->CompileShader(shader);
GLint status;
gl->GetShaderiv(shader, GL_COMPILE_STATUS, &status);
GLint log_length;
gl->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DBG2) : MSGL_ERR;
const char *typestr = type == GL_VERTEX_SHADER ? "vertex" : "fragment";
if (mp_msg_test(MSGT_VO, pri)) {
mp_msg(MSGT_VO, pri, "[gl] %s shader source:\n", typestr);
mp_log_source(MSGT_VO, pri, full_source);
}
if (log_length > 1) {
GLchar *log = talloc_zero_size(tmp, log_length + 1);
gl->GetShaderInfoLog(shader, log_length, NULL, log);
mp_msg(MSGT_VO, pri, "[gl] %s shader compile log (status=%d):\n%s\n",
typestr, status, log);
}
talloc_free(tmp);
return shader;
}
static void prog_create_shader(GL *gl, GLuint program, GLenum type,
const char *header, const char *source)
{
GLuint shader = create_shader(gl, type, header, source);
gl->AttachShader(program, shader);
gl->DeleteShader(shader);
}
static void link_shader(GL *gl, GLuint program)
{
gl->LinkProgram(program);
GLint status;
gl->GetProgramiv(program, GL_LINK_STATUS, &status);
GLint log_length;
gl->GetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DBG2) : MSGL_ERR;
if (mp_msg_test(MSGT_VO, pri)) {
GLchar *log = talloc_zero_size(NULL, log_length + 1);
gl->GetProgramInfoLog(program, log_length, NULL, log);
mp_msg(MSGT_VO, pri, "[gl] shader link log (status=%d): %s\n",
status, log);
talloc_free(log);
}
}
static void bind_attrib_locs(GL *gl, GLuint program)
{
gl->BindAttribLocation(program, VERTEX_ATTRIB_POSITION, "vertex_position");
gl->BindAttribLocation(program, VERTEX_ATTRIB_COLOR, "vertex_color");
gl->BindAttribLocation(program, VERTEX_ATTRIB_TEXCOORD, "vertex_texcoord");
}
static GLuint create_program(GL *gl, const char *name, const char *header,
const char *vertex, const char *frag)
{
mp_msg(MSGT_VO, MSGL_V, "[gl] compiling shader program '%s'\n", name);
mp_msg(MSGT_VO, MSGL_V, "[gl] header:\n");
mp_log_source(MSGT_VO, MSGL_V, header);
GLuint prog = gl->CreateProgram();
prog_create_shader(gl, prog, GL_VERTEX_SHADER, header, vertex);
prog_create_shader(gl, prog, GL_FRAGMENT_SHADER, header, frag);
bind_attrib_locs(gl, prog);
link_shader(gl, prog);
return prog;
}
static void shader_def(char **shader, const char *name,
const char *value)
{
*shader = talloc_asprintf_append(*shader, "#define %s %s\n", name, value);
}
static void shader_def_opt(char **shader, const char *name, bool b)
{
if (b)
shader_def(shader, name, "1");
}
static void shader_setup_scaler(char **shader, struct scaler *scaler, int pass)
{
const char *target = scaler->index == 0 ? "SAMPLE_L" : "SAMPLE_C";
if (!scaler->kernel) {
*shader = talloc_asprintf_append(*shader, "#define %s sample_%s\n",
target, scaler->name);
} else {
int size = scaler->kernel->size;
if (pass != -1) {
// The direction/pass assignment is rather arbitrary, but fixed in
// other parts of the code (like FBO setup).
const char *direction = pass == 0 ? "0, 1" : "1, 0";
*shader = talloc_asprintf_append(*shader, "#define %s(p0, p1, p2) "
"sample_convolution_sep%d(vec2(%s), %s, p0, p1, p2)\n",
target, size, direction, scaler->lut_name);
} else {
*shader = talloc_asprintf_append(*shader, "#define %s(p0, p1, p2) "
"sample_convolution%d(%s, p0, p1, p2)\n",
target, size, scaler->lut_name);
}
}
}
// return false if RGB or 4:4:4 YUV
static bool input_is_subsampled(struct gl_priv *p)
{
for (int i = 0; i < p->plane_count; i++)
if (p->planes[i].shift_x || p->planes[i].shift_y)
return true;
return false;
}
static void compile_shaders(struct gl_priv *p)
{
GL *gl = p->gl;
delete_shaders(p);
void *tmp = talloc_new(NULL);
struct bstr src = bstr0(vo_opengl_shaders);
char *vertex_shader = get_section(tmp, src, "vertex_all");
char *shader_prelude = get_section(tmp, src, "prelude");
char *s_video = get_section(tmp, src, "frag_video");
char *s_eosd = get_section(tmp, src, "frag_eosd");
char *s_osd = get_section(tmp, src, "frag_osd");
char *header = talloc_asprintf(tmp, "#version %d\n%s", gl->glsl_version,
shader_prelude);
char *header_eosd = talloc_strdup(tmp, header);
shader_def_opt(&header_eosd, "USE_3DLUT", p->use_lut_3d);
p->eosd_program =
create_program(gl, "eosd", header_eosd, vertex_shader, s_eosd);
p->osd_program =
create_program(gl, "osd", header, vertex_shader, s_osd);
char *header_conv = talloc_strdup(tmp, "");
char *header_final = talloc_strdup(tmp, "");
char *header_sep = NULL;
bool convert_input_to_linear = !p->is_linear_rgb
&& (p->use_srgb || p->use_lut_3d);
shader_def_opt(&header_conv, "USE_PLANAR", p->plane_count > 1);
shader_def_opt(&header_conv, "USE_GBRP", p->image_format == IMGFMT_GBRP);
shader_def_opt(&header_conv, "USE_YGRAY", p->is_yuv && p->plane_count == 1);
shader_def_opt(&header_conv, "USE_COLORMATRIX", p->is_yuv);
shader_def_opt(&header_conv, "USE_LINEAR_CONV", convert_input_to_linear);
shader_def_opt(&header_final, "USE_LINEAR_CONV_INV", p->use_lut_3d);
shader_def_opt(&header_final, "USE_GAMMA_POW", p->use_gamma);
shader_def_opt(&header_final, "USE_3DLUT", p->use_lut_3d);
shader_def_opt(&header_final, "USE_DITHER", p->dither_texture != 0);
if (p->use_scale_sep && p->scalers[0].kernel) {
header_sep = talloc_strdup(tmp, "");
shader_def_opt(&header_sep, "FIXED_SCALE", true);
shader_setup_scaler(&header_sep, &p->scalers[0], 0);
shader_setup_scaler(&header_final, &p->scalers[0], 1);
} else {
shader_setup_scaler(&header_final, &p->scalers[0], -1);
}
// We want to do scaling in linear light. Scaling is closely connected to
// texture sampling due to how the shader is structured (or if GL bilinear
// scaling is used). The purpose of the "indirect" pass is to convert the
// input video to linear RGB.
// Another purpose is reducing input to a single texture for scaling.
bool use_indirect = p->use_indirect;
// Don't sample from input video textures before converting the input to
// linear light. (Unneeded when sRGB textures are used.)
if (convert_input_to_linear)
use_indirect = true;
// It doesn't make sense to scale the chroma with cscale in the 1. scale
// step and with lscale in the 2. step. If the chroma is subsampled, a
// convolution filter wouldn't even work entirely correctly, because the
// luma scaler would sample two texels instead of one per tap for chroma.
// Also, even with 4:4:4 YUV or planar RGB, the indirection might be faster,
// because the shader can't use one scaler for sampling from 3 textures. It
// has to fetch the coefficients for each texture separately, even though
// they're the same (this is not an inherent restriction, but would require
// to restructure the shader).
if (header_sep && p->plane_count > 1)
use_indirect = true;
if (input_is_subsampled(p)) {
shader_setup_scaler(&header_conv, &p->scalers[1], -1);
} else {
// Force using the luma scaler on chroma. If the "indirect" stage is
// used, the actual scaling will happen in the next stage.
shader_def(&header_conv, "SAMPLE_C",
use_indirect ? "sample_bilinear" : "SAMPLE_L");
}
if (use_indirect) {
// We don't use filtering for the Y-plane (luma), because it's never
// scaled in this scenario.
shader_def(&header_conv, "SAMPLE_L", "sample_bilinear");
shader_def_opt(&header_conv, "FIXED_SCALE", true);
header_conv = t_concat(tmp, header, header_conv);
p->indirect_program =
create_program(gl, "indirect", header_conv, vertex_shader, s_video);
} else if (header_sep) {
header_sep = t_concat(tmp, header_sep, header_conv);
} else {
header_final = t_concat(tmp, header_final, header_conv);
}
if (header_sep) {
header_sep = t_concat(tmp, header, header_sep);
p->scale_sep_program =
create_program(gl, "scale_sep", header_sep, vertex_shader, s_video);
}
header_final = t_concat(tmp, header, header_final);
p->final_program =
create_program(gl, "final", header_final, vertex_shader, s_video);
debug_check_gl(p, "shader compilation");
talloc_free(tmp);
}
static void delete_program(GL *gl, GLuint *prog)
{
gl->DeleteProgram(*prog);
*prog = 0;
}
static void delete_shaders(struct gl_priv *p)
{
GL *gl = p->gl;
delete_program(gl, &p->osd_program);
delete_program(gl, &p->eosd_program);
delete_program(gl, &p->indirect_program);
delete_program(gl, &p->scale_sep_program);
delete_program(gl, &p->final_program);
}
static double get_scale_factor(struct gl_priv *p)
{
double sx = p->dst_rect.width / (double)p->src_rect.width;
double sy = p->dst_rect.height / (double)p->src_rect.height;
// xxx: actually we should use different scalers in X/Y directions if the
// scale factors are different due to anamorphic content
return FFMIN(sx, sy);
}
static bool update_scale_factor(struct gl_priv *p, struct filter_kernel *kernel)
{
double scale = get_scale_factor(p);
if (!p->use_fancy_downscaling && scale < 1.0)
scale = 1.0;
return mp_init_filter(kernel, filter_sizes, FFMAX(1.0, 1.0 / scale));
}
static void init_scaler(struct gl_priv *p, struct scaler *scaler)
{
GL *gl = p->gl;
assert(scaler->name);
scaler->kernel = NULL;
const struct filter_kernel *t_kernel = mp_find_filter_kernel(scaler->name);
if (!t_kernel)
return;
scaler->kernel_storage = *t_kernel;
scaler->kernel = &scaler->kernel_storage;
for (int n = 0; n < 2; n++) {
if (!isnan(p->scaler_params[n]))
scaler->kernel->params[n] = p->scaler_params[n];
}
update_scale_factor(p, scaler->kernel);
int size = scaler->kernel->size;
assert(size < FF_ARRAY_ELEMS(lut_tex_formats));
struct lut_tex_format *fmt = &lut_tex_formats[size];
bool use_2d = fmt->pixels > 1;
bool is_luma = scaler->index == 0;
scaler->lut_name = use_2d
? (is_luma ? "lut_l_2d" : "lut_c_2d")
: (is_luma ? "lut_l_1d" : "lut_c_1d");
gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_SCALERS + scaler->index);
GLenum target = use_2d ? GL_TEXTURE_2D : GL_TEXTURE_1D;
if (!scaler->gl_lut)
gl->GenTextures(1, &scaler->gl_lut);
gl->BindTexture(target, scaler->gl_lut);
gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
float *weights = talloc_array(NULL, float, LOOKUP_TEXTURE_SIZE * size);
mp_compute_lut(scaler->kernel, LOOKUP_TEXTURE_SIZE, weights);
if (use_2d) {
gl->TexImage2D(GL_TEXTURE_2D, 0, fmt->internal_format, fmt->pixels,
LOOKUP_TEXTURE_SIZE, 0, fmt->format, GL_FLOAT,
weights);
} else {
gl->TexImage1D(GL_TEXTURE_1D, 0, fmt->internal_format,
LOOKUP_TEXTURE_SIZE, 0, fmt->format, GL_FLOAT,
weights);
}
talloc_free(weights);
gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl->ActiveTexture(GL_TEXTURE0);
debug_check_gl(p, "after initializing scaler");
}
static void make_dither_matrix(unsigned char *m, int size)
{
m[0] = 0;
for (int sz = 1; sz < size; sz *= 2) {
int offset[] = {sz*size, sz, sz * (size+1), 0};
for (int i = 0; i < 4; i++)
for (int y = 0; y < sz * size; y += size)
for (int x = 0; x < sz; x++)
m[x+y+offset[i]] = m[x+y] * 4 + (3-i) * 256/size/size;
}
}
static void init_dither(struct gl_priv *p)
{
GL *gl = p->gl;
// Assume 8 bits per component if unknown.
int dst_depth = p->glctx->depth_g ? p->glctx->depth_g : 8;
if (p->dither_depth > 0)
dst_depth = p->dither_depth;
int src_depth = p->component_bits;
if (p->use_lut_3d)
src_depth = 16;
if (dst_depth >= src_depth || p->dither_depth < 0 || src_depth < 0)
return;
mp_msg(MSGT_VO, MSGL_V, "[gl] Dither %d->%d.\n", src_depth, dst_depth);
// This defines how many bits are considered significant for output on
// screen. The superfluous bits will be used for rounded according to the
// dither matrix. The precision of the source implicitly decides how many
// dither patterns can be visible.
p->dither_quantization = (1 << dst_depth) - 1;
int size = 8;
p->dither_multiply = p->dither_quantization + 1.0 / (size*size);
unsigned char dither[256];
make_dither_matrix(dither, size);
p->dither_size = size;
gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_DITHER);
gl->GenTextures(1, &p->dither_texture);
gl->BindTexture(GL_TEXTURE_2D, p->dither_texture);
gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1);
gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RED, size, size, 0, GL_RED,
GL_UNSIGNED_BYTE, dither);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
gl->ActiveTexture(GL_TEXTURE0);
}
static void reinit_rendering(struct gl_priv *p)
{
mp_msg(MSGT_VO, MSGL_V, "[gl] Reinit rendering.\n");
if (p->gl->SwapInterval && p->swap_interval >= 0)
p->gl->SwapInterval(p->swap_interval);
debug_check_gl(p, "before scaler initialization");
uninit_rendering(p);
init_dither(p);
init_scaler(p, &p->scalers[0]);
init_scaler(p, &p->scalers[1]);
compile_shaders(p);
if (p->indirect_program && !p->indirect_fbo.fbo)
fbotex_init(p, &p->indirect_fbo, p->texture_width, p->texture_height);
}
static void uninit_rendering(struct gl_priv *p)
{
GL *gl = p->gl;
delete_shaders(p);
for (int n = 0; n < 2; n++) {
gl->DeleteTextures(1, &p->scalers[n].gl_lut);
p->scalers[n].gl_lut = 0;
p->scalers[n].lut_name = NULL;
p->scalers[n].kernel = NULL;
}
gl->DeleteTextures(1, &p->dither_texture);
p->dither_texture = 0;
}
static void init_lut_3d(struct gl_priv *p)
{
GL *gl = p->gl;
gl->GenTextures(1, &p->lut_3d_texture);
gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_3DLUT);
gl->BindTexture(GL_TEXTURE_3D, p->lut_3d_texture);
gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
gl->TexImage3D(GL_TEXTURE_3D, 0, GL_RGB16, p->lut_3d_w, p->lut_3d_h,
p->lut_3d_d, 0, GL_RGB, GL_UNSIGNED_SHORT, p->lut_3d_data);
gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
gl->ActiveTexture(GL_TEXTURE0);
debug_check_gl(p, "after 3d lut creation");
}
static void init_video(struct gl_priv *p)
{
GL *gl = p->gl;
if (p->use_lut_3d && !p->lut_3d_texture)
init_lut_3d(p);
if (!p->is_yuv && (p->use_srgb || p->use_lut_3d)) {
p->is_linear_rgb = true;
p->gl_internal_format = GL_SRGB;
}
int eq_caps = MP_CSP_EQ_CAPS_GAMMA;
if (p->is_yuv)
eq_caps |= MP_CSP_EQ_CAPS_COLORMATRIX;
p->video_eq.capabilities = eq_caps;
debug_check_gl(p, "before video texture creation");
tex_size(p, p->image_width, p->image_height,
&p->texture_width, &p->texture_height);
for (int n = 0; n < p->plane_count; n++) {
struct texplane *plane = &p->planes[n];
int w = p->texture_width >> plane->shift_x;
int h = p->texture_height >> plane->shift_y;
mp_msg(MSGT_VO, MSGL_V, "[gl] Texture for plane %d: %dx%d\n", n, w, h);
gl->ActiveTexture(GL_TEXTURE0 + n);
gl->GenTextures(1, &plane->gl_texture);
gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture);
gl->TexImage2D(GL_TEXTURE_2D, 0, p->gl_internal_format, w, h, 0,
p->gl_format, p->gl_type, NULL);
default_tex_params(gl, GL_TEXTURE_2D, GL_LINEAR);
}
gl->ActiveTexture(GL_TEXTURE0);
debug_check_gl(p, "after video texture creation");
reinit_rendering(p);
}
static void uninit_video(struct gl_priv *p)
{
GL *gl = p->gl;
uninit_rendering(p);
for (int n = 0; n < 3; n++) {
struct texplane *plane = &p->planes[n];
gl->DeleteTextures(1, &plane->gl_texture);
plane->gl_texture = 0;
gl->DeleteBuffers(1, &plane->gl_buffer);
plane->gl_buffer = 0;
plane->buffer_ptr = NULL;
plane->buffer_size = 0;
}
fbotex_uninit(p, &p->indirect_fbo);
fbotex_uninit(p, &p->scale_sep_fbo);
}
static void render_to_fbo(struct gl_priv *p, struct fbotex *fbo, int w, int h,
int tex_w, int tex_h)
{
GL *gl = p->gl;
gl->Viewport(0, 0, fbo->vp_w, fbo->vp_h);
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
struct vertex vb[VERTICES_PER_QUAD];
write_quad(vb, -1, -1, 1, 1,
0, 0, w, h,
tex_w, tex_h,
NULL, false);
draw_triangles(p, vb, VERTICES_PER_QUAD);
gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
gl->Viewport(p->vp_x, p->vp_y, p->vp_w, p->vp_h);
}
static void handle_pass(struct gl_priv *p, struct fbotex **source,
struct fbotex *fbo, GLuint program)
{
GL *gl = p->gl;
if (!program)
return;
gl->BindTexture(GL_TEXTURE_2D, (*source)->texture);
gl->UseProgram(program);
render_to_fbo(p, fbo, (*source)->vp_w, (*source)->vp_h,
(*source)->tex_w, (*source)->tex_h);
*source = fbo;
}
static void do_render(struct gl_priv *p)
{
GL *gl = p->gl;
struct vertex vb[VERTICES_PER_QUAD];
bool is_flipped = p->mpi_flipped ^ p->vo_flipped;
// Order of processing:
// [indirect -> [scale_sep ->]] final
struct fbotex dummy = {
.vp_w = p->image_width, .vp_h = p->image_height,
.tex_w = p->texture_width, .tex_h = p->texture_height,
.texture = p->planes[0].gl_texture,
};
struct fbotex *source = &dummy;
handle_pass(p, &source, &p->indirect_fbo, p->indirect_program);
handle_pass(p, &source, &p->scale_sep_fbo, p->scale_sep_program);
gl->BindTexture(GL_TEXTURE_2D, source->texture);
gl->UseProgram(p->final_program);
float final_texw = p->image_width * source->tex_w / (float)source->vp_w;
float final_texh = p->image_height * source->tex_h / (float)source->vp_h;
bool use_srgb_fb = p->use_srgb && !p->use_lut_3d;
if (use_srgb_fb)
gl->Enable(GL_FRAMEBUFFER_SRGB);
if (p->stereo_mode) {
int w = p->src_rect.width;
int imgw = p->image_width;
glEnable3DLeft(gl, p->stereo_mode);
write_quad(vb,
p->dst_rect.left, p->dst_rect.top,
p->dst_rect.right, p->dst_rect.bottom,
p->src_rect.left / 2, p->src_rect.top,
p->src_rect.left / 2 + w / 2, p->src_rect.bottom,
final_texw, final_texh,
NULL, is_flipped);
draw_triangles(p, vb, VERTICES_PER_QUAD);
glEnable3DRight(gl, p->stereo_mode);
write_quad(vb,
p->dst_rect.left, p->dst_rect.top,
p->dst_rect.right, p->dst_rect.bottom,
p->src_rect.left / 2 + imgw / 2, p->src_rect.top,
p->src_rect.left / 2 + imgw / 2 + w / 2, p->src_rect.bottom,
final_texw, final_texh,
NULL, is_flipped);
draw_triangles(p, vb, VERTICES_PER_QUAD);
glDisable3D(gl, p->stereo_mode);
} else {
write_quad(vb,
p->dst_rect.left, p->dst_rect.top,
p->dst_rect.right, p->dst_rect.bottom,
p->src_rect.left, p->src_rect.top,
p->src_rect.right, p->src_rect.bottom,
final_texw, final_texh,
NULL, is_flipped);
draw_triangles(p, vb, VERTICES_PER_QUAD);
}
if (use_srgb_fb)
gl->Disable(GL_FRAMEBUFFER_SRGB);
gl->UseProgram(0);
debug_check_gl(p, "after video rendering");
}
static void update_window_sized_objects(struct gl_priv *p)
{
if (p->scale_sep_program) {
if (p->dst_rect.height > p->scale_sep_fbo.tex_h) {
fbotex_uninit(p, &p->scale_sep_fbo);
// Round up to an arbitrary alignment to make window resizing or
// panscan controls smoother (less texture reallocations).
int height = FFALIGN(p->dst_rect.height, 256);
fbotex_init(p, &p->scale_sep_fbo, p->image_width, height);
}
p->scale_sep_fbo.vp_w = p->image_width;
p->scale_sep_fbo.vp_h = p->dst_rect.height;
}
}
static void resize(struct gl_priv *p)
{
GL *gl = p->gl;
struct vo *vo = p->vo;
mp_msg(MSGT_VO, MSGL_V, "[gl] Resize: %dx%d\n", vo->dwidth, vo->dheight);
p->vp_x = 0, p->vp_y = 0;
if (WinID >= 0) {
int w = vo->dwidth, h = vo->dheight;
int old_y = vo->dheight;
geometry(&p->vp_x, &p->vp_y, &w, &h, vo->dwidth, vo->dheight);
p->vp_y = old_y - h - p->vp_y;
}
p->vp_w = vo->dwidth, p->vp_h = vo->dheight;
gl->Viewport(p->vp_x, p->vp_y, p->vp_w, p->vp_h);
struct vo_rect borders;
calc_src_dst_rects(vo, p->image_width, p->image_height, &p->src_rect,
&p->dst_rect, &borders, NULL);
p->border_x = borders.left;
p->border_y = borders.top;
bool need_scaler_reinit = false; // filter size change needed
bool need_scaler_update = false; // filter LUT change needed
bool too_small = false;
for (int n = 0; n < 2; n++) {
if (p->scalers[n].kernel) {
struct filter_kernel tkernel = *p->scalers[n].kernel;
struct filter_kernel old = tkernel;
bool ok = update_scale_factor(p, &tkernel);
too_small |= !ok;
need_scaler_reinit |= (tkernel.size != old.size);
need_scaler_update |= (tkernel.inv_scale != old.inv_scale);
}
}
if (need_scaler_reinit) {
reinit_rendering(p);
} else if (need_scaler_update) {
init_scaler(p, &p->scalers[0]);
init_scaler(p, &p->scalers[1]);
}
if (too_small)
mp_msg(MSGT_VO, MSGL_WARN, "[gl] Can't downscale that much, window "
"output may look suboptimal.\n");
update_window_sized_objects(p);
update_all_uniforms(p);
vo_osd_resized();
gl->Clear(GL_COLOR_BUFFER_BIT);
vo->want_redraw = true;
}
static void flip_page(struct vo *vo)
{
struct gl_priv *p = vo->priv;
GL *gl = p->gl;
if (p->use_glFinish)
gl->Finish();
p->glctx->swapGlBuffers(p->glctx);
if (p->dst_rect.left > p->vp_x || p->dst_rect.top > p->vp_y
|| p->dst_rect.right < p->vp_x + p->vp_w
|| p->dst_rect.bottom < p->vp_y + p->vp_h)
{
gl->Clear(GL_COLOR_BUFFER_BIT);
}
p->frames_rendered++;
}
static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h,
int x, int y)
{
struct gl_priv *p = vo->priv;
GL *gl = p->gl;
p->mpi_flipped = stride[0] < 0;
for (int n = 0; n < p->plane_count; n++) {
gl->ActiveTexture(GL_TEXTURE0 + n);
gl->BindTexture(GL_TEXTURE_2D, p->planes[n].gl_texture);
int xs = p->planes[n].shift_x, ys = p->planes[n].shift_y;
glUploadTex(gl, GL_TEXTURE_2D, p->gl_format, p->gl_type, src[n],
stride[n], x >> xs, y >> ys, w >> xs, h >> ys, 0);
}
gl->ActiveTexture(GL_TEXTURE0);
return 0;
}
static uint32_t get_image(struct vo *vo, mp_image_t *mpi)
{
struct gl_priv *p = vo->priv;
GL *gl = p->gl;
if (!p->use_pbo)
return VO_FALSE;
// We don't support alpha planes. (Disabling PBOs with normal draw calls is
// an undesired, but harmless side-effect.)
if (mpi->num_planes != p->plane_count)
return VO_FALSE;
if (mpi->flags & MP_IMGFLAG_READABLE)
return VO_FALSE;
if (mpi->type != MP_IMGTYPE_STATIC && mpi->type != MP_IMGTYPE_TEMP &&
(mpi->type != MP_IMGTYPE_NUMBERED || mpi->number))
return VO_FALSE;
mpi->flags &= ~MP_IMGFLAG_COMMON_PLANE;
for (int n = 0; n < p->plane_count; n++) {
struct texplane *plane = &p->planes[n];
mpi->stride[n] = (mpi->width >> plane->shift_x) * p->plane_bytes;
int needed_size = (mpi->height >> plane->shift_y) * mpi->stride[n];
if (!plane->gl_buffer)
gl->GenBuffers(1, &plane->gl_buffer);
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
if (needed_size > plane->buffer_size) {
plane->buffer_size = needed_size;
gl->BufferData(GL_PIXEL_UNPACK_BUFFER, plane->buffer_size,
NULL, GL_DYNAMIC_DRAW);
}
if (!plane->buffer_ptr)
plane->buffer_ptr = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER,
GL_WRITE_ONLY);
mpi->planes[n] = plane->buffer_ptr;
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
mpi->flags |= MP_IMGFLAG_DIRECT;
return VO_TRUE;
}
static uint32_t draw_image(struct gl_priv *p, mp_image_t *mpi)
{
GL *gl = p->gl;
int n;
assert(mpi->num_planes >= p->plane_count);
mp_image_t mpi2 = *mpi;
int w = mpi->w, h = mpi->h;
if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK)
goto skip_upload;
mpi2.flags = 0;
mpi2.type = MP_IMGTYPE_TEMP;
mpi2.width = mpi2.w;
mpi2.height = mpi2.h;
if (!(mpi->flags & MP_IMGFLAG_DIRECT)
&& !p->planes[0].buffer_ptr
&& get_image(p->vo, &mpi2) == VO_TRUE)
{
for (n = 0; n < p->plane_count; n++) {
struct texplane *plane = &p->planes[n];
int xs = plane->shift_x, ys = plane->shift_y;
int line_bytes = (mpi->w >> xs) * p->plane_bytes;
memcpy_pic(mpi2.planes[n], mpi->planes[n], line_bytes, mpi->h >> ys,
mpi2.stride[n], mpi->stride[n]);
}
mpi = &mpi2;
}
p->mpi_flipped = mpi->stride[0] < 0;
for (n = 0; n < p->plane_count; n++) {
struct texplane *plane = &p->planes[n];
int xs = plane->shift_x, ys = plane->shift_y;
void *plane_ptr = mpi->planes[n];
if (mpi->flags & MP_IMGFLAG_DIRECT) {
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER))
mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Video PBO upload failed. "
"Remove the 'pbo' suboption.\n");
plane->buffer_ptr = NULL;
plane_ptr = NULL; // PBO offset 0
}
gl->ActiveTexture(GL_TEXTURE0 + n);
gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture);
glUploadTex(gl, GL_TEXTURE_2D, p->gl_format, p->gl_type, plane_ptr,
mpi->stride[n], 0, 0, w >> xs, h >> ys, 0);
}
gl->ActiveTexture(GL_TEXTURE0);
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
skip_upload:
do_render(p);
return VO_TRUE;
}
static mp_image_t *get_screenshot(struct gl_priv *p)
{
GL *gl = p->gl;
mp_image_t *image = alloc_mpi(p->texture_width, p->texture_height,
p->image_format);
// NOTE about image formats with alpha plane: we don't even have the alpha
// anymore. We never upload it to any texture, as it would be a waste of
// time. On the other hand, we can't find a "similar", non-alpha image
// format easily. So we just leave the alpha plane of the newly allocated
// image as-is, and hope that the alpha is ignored by the receiver of the
// screenshot. (If not, code should be added to make it fully opaque.)
for (int n = 0; n < p->plane_count; n++) {
gl->ActiveTexture(GL_TEXTURE0 + n);
gl->BindTexture(GL_TEXTURE_2D, p->planes[n].gl_texture);
glDownloadTex(gl, GL_TEXTURE_2D, p->gl_format, p->gl_type,
image->planes[n], image->stride[n]);
}
gl->ActiveTexture(GL_TEXTURE0);
image->width = p->image_width;
image->height = p->image_height;
image->w = p->vo->aspdat.prew;
image->h = p->vo->aspdat.preh;
return image;
}
static mp_image_t *get_window_screenshot(struct gl_priv *p)
{
GL *gl = p->gl;
mp_image_t *image = alloc_mpi(p->vp_w, p->vp_h, IMGFMT_RGB24);
gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
gl->PixelStorei(GL_PACK_ROW_LENGTH, 0);
gl->ReadBuffer(GL_FRONT);
// flip image while reading
for (int y = 0; y < p->vp_h; y++) {
gl->ReadPixels(p->vp_x, p->vp_y + p->vp_h - y - 1, p->vp_w, 1,
GL_RGB, GL_UNSIGNED_BYTE,
image->planes[0] + y * image->stride[0]);
}
return image;
}
static void clear_osd(struct gl_priv *p)
{
GL *gl = p->gl;
if (!p->osd_textures_count)
return;
gl->DeleteTextures(p->osd_textures_count, p->osd_textures);
p->osd_textures_count = 0;
}
static void create_osd_texture(void *ctx, int x0, int y0, int w, int h,
unsigned char *src, unsigned char *srca,
int stride)
{
struct gl_priv *p = ctx;
GL *gl = p->gl;
if (w <= 0 || h <= 0 || stride < w) {
mp_msg(MSGT_VO, MSGL_V, "Invalid dimensions OSD for part!\n");
return;
}
if (p->osd_textures_count >= MAX_OSD_PARTS) {
mp_msg(MSGT_VO, MSGL_ERR, "Too many OSD parts, contact the developers!\n");
return;
}
int sx, sy;
tex_size(p, w, h, &sx, &sy);
gl->GenTextures(1, &p->osd_textures[p->osd_textures_count]);
gl->BindTexture(GL_TEXTURE_2D, p->osd_textures[p->osd_textures_count]);
gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RG, sx, sy, 0, GL_RG, GL_UNSIGNED_BYTE,
NULL);
default_tex_params(gl, GL_TEXTURE_2D, GL_NEAREST);
unsigned char *tmp = malloc(stride * h * 2);
// Convert alpha from weird MPlayer scale.
for (int i = 0; i < h * stride; i++) {
tmp[i*2+0] = src[i];
tmp[i*2+1] = -srca[i];
}
glUploadTex(gl, GL_TEXTURE_2D, GL_RG, GL_UNSIGNED_BYTE, tmp, stride * 2,
0, 0, w, h, 0);
free(tmp);
gl->BindTexture(GL_TEXTURE_2D, 0);
uint8_t color[4] = {(p->osd_color >> 16) & 0xff, (p->osd_color >> 8) & 0xff,
p->osd_color & 0xff, 0xff - (p->osd_color >> 24)};
write_quad(&p->osd_va[p->osd_textures_count * VERTICES_PER_QUAD],
x0, y0, x0 + w, y0 + h, 0, 0, w, h,
sx, sy, color, false);
p->osd_textures_count++;
}
static void draw_osd(struct vo *vo, struct osd_state *osd)
{
struct gl_priv *p = vo->priv;
GL *gl = p->gl;
if (vo_osd_has_changed(osd)) {
clear_osd(p);
osd_draw_text_ext(osd, vo->dwidth, vo->dheight, p->border_x,
p->border_y, p->border_x,
p->border_y, p->image_width,
p->image_height, create_osd_texture, p);
}
if (p->osd_textures_count > 0) {
gl->Enable(GL_BLEND);
// OSD bitmaps use premultiplied alpha.
gl->BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
gl->UseProgram(p->osd_program);
for (int n = 0; n < p->osd_textures_count; n++) {
gl->BindTexture(GL_TEXTURE_2D, p->osd_textures[n]);
draw_triangles(p, &p->osd_va[n * VERTICES_PER_QUAD],
VERTICES_PER_QUAD);
}
gl->UseProgram(0);
gl->Disable(GL_BLEND);
gl->BindTexture(GL_TEXTURE_2D, 0);
}
}
static void gen_eosd(struct gl_priv *p, mp_eosd_images_t *imgs)
{
GL *gl = p->gl;
bool need_repos, need_upload, need_allocate;
eosd_packer_generate(p->eosd, imgs, &need_repos, &need_upload,
&need_allocate);
if (!need_repos)
return;
if (!p->eosd_texture) {
gl->GenTextures(1, &p->eosd_texture);
gl->GenBuffers(1, &p->eosd_buffer);
}
gl->BindTexture(GL_TEXTURE_2D, p->eosd_texture);
if (need_allocate) {
tex_size(p, p->eosd->surface.w, p->eosd->surface.h,
&p->eosd_texture_width, &p->eosd_texture_height);
gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RED,
p->eosd_texture_width, p->eosd_texture_height, 0,
GL_RED, GL_UNSIGNED_BYTE, NULL);
default_tex_params(gl, GL_TEXTURE_2D, GL_NEAREST);
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->eosd_buffer);
gl->BufferData(GL_PIXEL_UNPACK_BUFFER,
p->eosd->surface.w * p->eosd->surface.h,
NULL,
GL_DYNAMIC_COPY);
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
p->eosd_va = talloc_realloc_size(p->eosd, p->eosd_va,
p->eosd->targets_count
* sizeof(struct vertex)
* VERTICES_PER_QUAD);
if (need_upload && p->use_pbo && p->eosd->targets_count) {
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->eosd_buffer);
char *data = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
if (!data) {
mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Error: can't upload subtitles! "
"Subtitles will look corrupted.\n");
} else {
for (int n = 0; n < p->eosd->targets_count; n++) {
struct eosd_target *target = &p->eosd->targets[n];
ASS_Image *i = target->ass_img;
void *pdata = data + target->source.y0 * p->eosd->surface.w
+ target->source.x0;
memcpy_pic(pdata, i->bitmap, i->w, i->h,
p->eosd->surface.w, i->stride);
}
if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER))
mp_msg(MSGT_VO, MSGL_FATAL, "[gl] EOSD PBO upload failed. "
"Remove the 'pbo' suboption.\n");
struct eosd_rect rc;
eosd_packer_calculate_source_bb(p->eosd, &rc);
glUploadTex(gl, GL_TEXTURE_2D, GL_RED, GL_UNSIGNED_BYTE, NULL,
p->eosd->surface.w, rc.x0, rc.y0,
rc.x1 - rc.x0, rc.y1 - rc.y0, 0);
}
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
} else if (need_upload) {
// non-PBO upload
for (int n = 0; n < p->eosd->targets_count; n++) {
struct eosd_target *target = &p->eosd->targets[n];
ASS_Image *i = target->ass_img;
glUploadTex(gl, GL_TEXTURE_2D, GL_RED, GL_UNSIGNED_BYTE, i->bitmap,
i->stride, target->source.x0, target->source.y0,
i->w, i->h, 0);
}
}
gl->BindTexture(GL_TEXTURE_2D, 0);
debug_check_gl(p, "EOSD upload");
for (int n = 0; n < p->eosd->targets_count; n++) {
struct eosd_target *target = &p->eosd->targets[n];
ASS_Image *i = target->ass_img;
uint8_t color[4] = { i->color >> 24, (i->color >> 16) & 0xff,
(i->color >> 8) & 0xff, 255 - (i->color & 0xff) };
write_quad(&p->eosd_va[n * VERTICES_PER_QUAD],
target->dest.x0, target->dest.y0,
target->dest.x1, target->dest.y1,
target->source.x0, target->source.y0,
target->source.x1, target->source.y1,
p->eosd_texture_width, p->eosd_texture_height,
color, false);
}
}
static void draw_eosd(struct gl_priv *p, mp_eosd_images_t *imgs)
{
GL *gl = p->gl;
gen_eosd(p, imgs);
if (p->eosd->targets_count == 0)
return;
gl->Enable(GL_BLEND);
gl->BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
gl->BindTexture(GL_TEXTURE_2D, p->eosd_texture);
gl->UseProgram(p->eosd_program);
draw_triangles(p, p->eosd_va, p->eosd->targets_count * VERTICES_PER_QUAD);
gl->UseProgram(0);
gl->BindTexture(GL_TEXTURE_2D, 0);
gl->Disable(GL_BLEND);
}
// Disable features that are not supported with the current OpenGL version.
static void check_gl_features(struct gl_priv *p)
{
GL *gl = p->gl;
bool have_float_tex = gl->mpgl_caps & MPGL_CAP_FLOAT_TEX;
bool have_fbo = gl->mpgl_caps & MPGL_CAP_FB;
bool have_srgb = (gl->mpgl_caps & MPGL_CAP_SRGB_TEX) &&
(gl->mpgl_caps & MPGL_CAP_SRGB_FB);
char *disabled[10];
int n_disabled = 0;
if (have_fbo) {
struct fbotex fbo = {0};
have_fbo = fbotex_init(p, &fbo, 16, 16);
fbotex_uninit(p, &fbo);
}
// Disable these only if the user didn't disable scale-sep on the command
// line, so convolution filter can still be forced to be run.
// Normally, we want to disable them by default if FBOs are unavailable,
// because they will be slow (not critically slow, but still slower).
// Without FP textures, we must always disable them.
if (!have_float_tex || (!have_fbo && p->use_scale_sep)) {
for (int n = 0; n < 2; n++) {
struct scaler *scaler = &p->scalers[n];
if (mp_find_filter_kernel(scaler->name)) {
scaler->name = "bilinear";
disabled[n_disabled++]
= have_float_tex ? "scaler (FBO)" : "scaler (float tex.)";
}
}
}
if (!have_srgb && p->use_srgb) {
p->use_srgb = false;
disabled[n_disabled++] = "sRGB";
}
if (!have_fbo && p->use_lut_3d) {
p->use_lut_3d = false;
disabled[n_disabled++] = "color management (FBO)";
}
if (!have_srgb && p->use_lut_3d) {
p->use_lut_3d = false;
disabled[n_disabled++] = "color management (sRGB)";
}
if (!have_fbo) {
p->use_scale_sep = false;
p->use_indirect = false;
}
if (n_disabled) {
mp_msg(MSGT_VO, MSGL_ERR, "[gl] Some OpenGL extensions not detected, "
"disabling: ");
for (int n = 0; n < n_disabled; n++) {
if (n)
mp_msg(MSGT_VO, MSGL_ERR, ", ");
mp_msg(MSGT_VO, MSGL_ERR, "%s", disabled[n]);
}
mp_msg(MSGT_VO, MSGL_ERR, ".\n");
}
}
static void setup_vertex_array(GL *gl)
{
size_t stride = sizeof(struct vertex);
gl->EnableVertexAttribArray(VERTEX_ATTRIB_POSITION);
gl->VertexAttribPointer(VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE,
stride, (void*)offsetof(struct vertex, position));
gl->EnableVertexAttribArray(VERTEX_ATTRIB_COLOR);
gl->VertexAttribPointer(VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE,
stride, (void*)offsetof(struct vertex, color));
gl->EnableVertexAttribArray(VERTEX_ATTRIB_TEXCOORD);
gl->VertexAttribPointer(VERTEX_ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE,
stride, (void*)offsetof(struct vertex, texcoord));
}
static int init_gl(struct gl_priv *p)
{
GL *gl = p->gl;
debug_check_gl(p, "before init_gl");
const char *vendor = gl->GetString(GL_VENDOR);
const char *version = gl->GetString(GL_VERSION);
const char *renderer = gl->GetString(GL_RENDERER);
const char *glsl = gl->GetString(GL_SHADING_LANGUAGE_VERSION);
mp_msg(MSGT_VO, MSGL_V, "[gl] GL_RENDERER='%s', GL_VENDOR='%s', "
"GL_VERSION='%s', GL_SHADING_LANGUAGE_VERSION='%s'"
"\n", renderer, vendor, version, glsl);
mp_msg(MSGT_VO, MSGL_V, "[gl] Display depth: R=%d, G=%d, B=%d\n",
p->glctx->depth_r, p->glctx->depth_g, p->glctx->depth_b);
check_gl_features(p);
gl->Disable(GL_DITHER);
gl->Disable(GL_BLEND);
gl->Disable(GL_DEPTH_TEST);
gl->DepthMask(GL_FALSE);
gl->Disable(GL_CULL_FACE);
gl->DrawBuffer(GL_BACK);
gl->GenBuffers(1, &p->vertex_buffer);
gl->BindBuffer(GL_ARRAY_BUFFER, p->vertex_buffer);
if (gl->BindVertexArray) {
gl->GenVertexArrays(1, &p->vao);
gl->BindVertexArray(p->vao);
setup_vertex_array(gl);
gl->BindVertexArray(0);
} else {
setup_vertex_array(gl);
}
gl->BindBuffer(GL_ARRAY_BUFFER, 0);
GLint max_texture_size;
gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
eosd_packer_reinit(p->eosd, max_texture_size, max_texture_size);
gl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl->Clear(GL_COLOR_BUFFER_BIT);
debug_check_gl(p, "after init_gl");
return 1;
}
static void uninit_gl(struct gl_priv *p)
{
GL *gl = p->gl;
// NOTE: GL functions might not be loaded yet
if (!(p->glctx && p->gl->DeleteTextures))
return;
uninit_video(p);
if (gl->DeleteVertexArrays)
gl->DeleteVertexArrays(1, &p->vao);
p->vao = 0;
gl->DeleteBuffers(1, &p->vertex_buffer);
p->vertex_buffer = 0;
clear_osd(p);
gl->DeleteTextures(1, &p->eosd_texture);
p->eosd_texture = 0;
gl->DeleteBuffers(1, &p->eosd_buffer);
p->eosd_buffer = 0;
eosd_packer_reinit(p->eosd, 0, 0);
gl->DeleteTextures(1, &p->lut_3d_texture);
p->lut_3d_texture = 0;
}
static bool init_format(int fmt, struct gl_priv *init)
{
bool supported = false;
struct gl_priv dummy;
if (!init)
init = &dummy;
mp_image_t dummy_img = {0};
mp_image_setfmt(&dummy_img, fmt);
init->image_format = fmt;
init->component_bits = -1;
// RGB/packed formats
for (const struct fmt_entry *e = mp_to_gl_formats; e->mp_format; e++) {
if (e->mp_format == fmt) {
supported = true;
init->plane_bits = dummy_img.bpp;
init->gl_format = e->format;
init->gl_internal_format = e->internal_format;
init->component_bits = e->component_bits;
init->gl_type = e->type;
break;
}
}
// YUV/planar formats
if (!supported && mp_get_chroma_shift(fmt, NULL, NULL, &init->plane_bits)) {
init->gl_format = GL_RED;
init->component_bits = init->plane_bits;
if (init->plane_bits == 8) {
supported = true;
init->gl_internal_format = GL_RED;
init->gl_type = GL_UNSIGNED_BYTE;
} else if (IMGFMT_IS_YUVP16_NE(fmt)) {
supported = true;
init->gl_internal_format = GL_R16;
init->gl_type = GL_UNSIGNED_SHORT;
}
}
// RGB/planar
if (!supported && fmt == IMGFMT_GBRP) {
supported = true;
init->plane_bits = init->component_bits = 8;
init->gl_format = GL_RED;
init->gl_internal_format = GL_RED;
init->gl_type = GL_UNSIGNED_BYTE;
}
if (!supported)
return false;
init->plane_bytes = (init->plane_bits + 7) / 8;
init->is_yuv = dummy_img.flags & MP_IMGFLAG_YUV;
init->is_linear_rgb = false;
// NOTE: we throw away the additional alpha plane, if one exists.
init->plane_count = dummy_img.num_planes > 2 ? 3 : 1;
assert(dummy_img.num_planes >= init->plane_count);
assert(dummy_img.num_planes <= init->plane_count + 1);
for (int n = 0; n < init->plane_count; n++) {
struct texplane *plane = &init->planes[n];
plane->shift_x = n > 0 ? dummy_img.chroma_x_shift : 0;
plane->shift_y = n > 0 ? dummy_img.chroma_y_shift : 0;
}
return true;
}
static int query_format(uint32_t format)
{
int caps = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_FLIP |
VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_ACCEPT_STRIDE |
VFCAP_OSD | VFCAP_EOSD | VFCAP_EOSD_UNSCALED;
if (!init_format(format, NULL))
return 0;
return caps;
}
static bool create_window(struct gl_priv *p, uint32_t d_width,
uint32_t d_height, uint32_t flags)
{
if (p->stereo_mode == GL_3D_QUADBUFFER)
flags |= VOFLAG_STEREO;
if (p->use_gl_debug)
flags |= VOFLAG_GL_DEBUG;
int mpgl_caps = MPGL_CAP_GL21 | MPGL_CAP_TEX_RG;
if (!p->allow_sw)
mpgl_caps |= MPGL_CAP_NO_SW;
return mpgl_create_window(p->glctx, mpgl_caps, d_width, d_height, flags);
}
static int config(struct vo *vo, uint32_t width, uint32_t height,
uint32_t d_width, uint32_t d_height, uint32_t flags,
uint32_t format)
{
struct gl_priv *p = vo->priv;
if (!create_window(p, d_width, d_height, flags))
return -1;
if (!p->vertex_buffer)
init_gl(p);
p->vo_flipped = !!(flags & VOFLAG_FLIPPING);
if (p->image_format != format || p->image_width != width
|| p->image_height != height)
{
uninit_video(p);
p->image_height = height;
p->image_width = width;
init_format(format, p);
init_video(p);
}
resize(p);
return 0;
}
static void check_events(struct vo *vo)
{
struct gl_priv *p = vo->priv;
int e = p->glctx->check_events(vo);
if (e & VO_EVENT_REINIT) {
uninit_gl(p);
init_gl(p);
init_video(p);
resize(p);
}
if (e & VO_EVENT_RESIZE)
resize(p);
if (e & VO_EVENT_EXPOSE)
vo->want_redraw = true;
}
static int control(struct vo *vo, uint32_t request, void *data)
{
struct gl_priv *p = vo->priv;
switch (request) {
case VOCTRL_QUERY_FORMAT:
return query_format(*(uint32_t *)data);
case VOCTRL_DRAW_IMAGE:
return draw_image(p, data);
case VOCTRL_DRAW_EOSD:
if (!data)
return VO_FALSE;
draw_eosd(p, data);
return VO_TRUE;
case VOCTRL_GET_EOSD_RES: {
mp_eosd_res_t *r = data;
r->w = vo->dwidth;
r->h = vo->dheight;
r->ml = r->mr = p->border_x;
r->mt = r->mb = p->border_y;
return VO_TRUE;
}
case VOCTRL_ONTOP:
if (!p->glctx->ontop)
break;
p->glctx->ontop(vo);
return VO_TRUE;
case VOCTRL_PAUSE:
if (!p->glctx->pause)
break;
p->glctx->pause(vo);
return VO_TRUE;
case VOCTRL_RESUME:
if (!p->glctx->resume)
break;
p->glctx->resume(vo);
return VO_TRUE;
case VOCTRL_FULLSCREEN:
p->glctx->fullscreen(vo);
resize(p);
return VO_TRUE;
case VOCTRL_BORDER:
if (!p->glctx->border)
break;
p->glctx->border(vo);
resize(p);
return VO_TRUE;
case VOCTRL_GET_PANSCAN:
return VO_TRUE;
case VOCTRL_SET_PANSCAN:
resize(p);
return VO_TRUE;
case VOCTRL_GET_EQUALIZER: {
struct voctrl_get_equalizer_args *args = data;
return mp_csp_equalizer_get(&p->video_eq, args->name, args->valueptr)
>= 0 ? VO_TRUE : VO_NOTIMPL;
}
case VOCTRL_SET_EQUALIZER: {
struct voctrl_set_equalizer_args *args = data;
if (mp_csp_equalizer_set(&p->video_eq, args->name, args->value) < 0)
return VO_NOTIMPL;
if (!p->use_gamma && p->video_eq.values[MP_CSP_EQ_GAMMA] != 0) {
mp_msg(MSGT_VO, MSGL_V, "[gl] Auto-enabling gamma.\n");
p->use_gamma = true;
compile_shaders(p);
}
update_all_uniforms(p);
vo->want_redraw = true;
return VO_TRUE;
}
case VOCTRL_SET_YUV_COLORSPACE: {
if (p->is_yuv) {
p->colorspace = *(struct mp_csp_details *)data;
update_all_uniforms(p);
vo->want_redraw = true;
}
return VO_TRUE;
}
case VOCTRL_GET_YUV_COLORSPACE:
*(struct mp_csp_details *)data = p->colorspace;
return VO_TRUE;
case VOCTRL_UPDATE_SCREENINFO:
if (!p->glctx->update_xinerama_info)
break;
p->glctx->update_xinerama_info(vo);
return VO_TRUE;
case VOCTRL_SCREENSHOT: {
struct voctrl_screenshot_args *args = data;
if (args->full_window)
args->out_image = get_window_screenshot(p);
else
args->out_image = get_screenshot(p);
return true;
}
case VOCTRL_REDRAW_FRAME:
do_render(p);
return true;
case VOCTRL_SET_COMMAND_LINE: {
char *arg = data;
if (!reparse_cmdline(p, arg))
return false;
check_gl_features(p);
reinit_rendering(p);
resize(p);
vo->want_redraw = true;
return true;
}
}
return VO_NOTIMPL;
}
static void uninit(struct vo *vo)
{
struct gl_priv *p = vo->priv;
uninit_gl(p);
mpgl_uninit(p->glctx);
p->glctx = NULL;
p->gl = NULL;
}
#ifdef CONFIG_LCMS2
static void lcms2_error_handler(cmsContext ctx, cmsUInt32Number code,
const char *msg)
{
mp_msg(MSGT_VO, MSGL_ERR, "[gl] lcms2: %s\n", msg);
}
static struct bstr load_file(struct gl_priv *p, void *talloc_ctx,
const char *filename)
{
struct bstr res = {0};
stream_t *s = open_stream(filename, p->vo->opts, NULL);
if (s) {
res = stream_read_complete(s, talloc_ctx, 1000000000, 0);
free_stream(s);
}
return res;
}
#define LUT3D_CACHE_HEADER "mpv 3dlut cache 1.0\n"
static bool load_icc(struct gl_priv *p, const char *icc_file,
const char *icc_cache, int icc_intent,
int s_r, int s_g, int s_b)
{
void *tmp = talloc_new(p);
uint16_t *output = talloc_array(tmp, uint16_t, s_r * s_g * s_b * 3);
if (icc_intent == -1)
icc_intent = INTENT_ABSOLUTE_COLORIMETRIC;
mp_msg(MSGT_VO, MSGL_INFO, "[gl] Opening ICC profile '%s'\n", icc_file);
struct bstr iccdata = load_file(p, tmp, icc_file);
if (!iccdata.len)
goto error_exit;
char *cache_info = talloc_asprintf(tmp, "intent=%d, size=%dx%dx%d\n",
icc_intent, s_r, s_g, s_b);
// check cache
if (icc_cache) {
mp_msg(MSGT_VO, MSGL_INFO, "[gl] Opening 3D LUT cache in file '%s'.\n",
icc_cache);
struct bstr cachedata = load_file(p, tmp, icc_cache);
if (bstr_eatstart(&cachedata, bstr0(LUT3D_CACHE_HEADER))
&& bstr_eatstart(&cachedata, bstr0(cache_info))
&& bstr_eatstart(&cachedata, iccdata)
&& cachedata.len == talloc_get_size(output))
{
memcpy(output, cachedata.start, cachedata.len);
goto done;
} else {
mp_msg(MSGT_VO, MSGL_WARN, "[gl] 3D LUT cache invalid!\n");
}
}
cmsSetLogErrorHandler(lcms2_error_handler);
cmsHPROFILE profile = cmsOpenProfileFromMem(iccdata.start, iccdata.len);
if (!profile)
goto error_exit;
cmsCIExyY d65;
cmsWhitePointFromTemp(&d65, 6504);
static const cmsCIExyYTRIPLE bt709prim = {
.Red = {0.64, 0.33, 1.0},
.Green = {0.30, 0.60, 1.0},
.Blue = {0.15, 0.06, 1.0},
};
cmsToneCurve *tonecurve = cmsBuildGamma(NULL, 2.2);
cmsHPROFILE vid_profile = cmsCreateRGBProfile(&d65, &bt709prim,
(cmsToneCurve*[3]){tonecurve, tonecurve, tonecurve});
cmsFreeToneCurve(tonecurve);
cmsHTRANSFORM trafo = cmsCreateTransform(vid_profile, TYPE_RGB_16,
profile, TYPE_RGB_16,
icc_intent,
cmsFLAGS_HIGHRESPRECALC);
cmsCloseProfile(profile);
cmsCloseProfile(vid_profile);
if (!trafo)
goto error_exit;
// transform a (s_r)x(s_g)x(s_b) cube, with 3 components per channel
uint16_t *input = talloc_array(tmp, uint16_t, s_r * 3);
for (int b = 0; b < s_b; b++) {
for (int g = 0; g < s_g; g++) {
for (int r = 0; r < s_r; r++) {
input[r * 3 + 0] = r * 65535 / (s_r - 1);
input[r * 3 + 1] = g * 65535 / (s_g - 1);
input[r * 3 + 2] = b * 65535 / (s_b - 1);
}
size_t base = (b * s_r * s_g + g * s_r) * 3;
cmsDoTransform(trafo, input, output + base, s_r);
}
}
cmsDeleteTransform(trafo);
if (icc_cache) {
FILE *out = fopen(icc_cache, "wb");
if (out) {
fprintf(out, "%s%s", LUT3D_CACHE_HEADER, cache_info);
fwrite(iccdata.start, iccdata.len, 1, out);
fwrite(output, talloc_get_size(output), 1, out);
fclose(out);
}
}
done:
p->lut_3d_data = talloc_steal(p, output);
p->lut_3d_w = s_r, p->lut_3d_h = s_g, p->lut_3d_d = s_b;
p->use_lut_3d = true;
talloc_free(tmp);
return true;
error_exit:
mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Error loading ICC profile.\n");
talloc_free(tmp);
return false;
}
#else /* CONFIG_LCMS2 */
static bool load_icc(struct gl_priv *p, ...)
{
mp_msg(MSGT_VO, MSGL_FATAL, "[gl] LCMS2 support not compiled.\n");
return false;
}
#endif /* CONFIG_LCMS2 */
static bool parse_3dlut_size(const char *s, int *p1, int *p2, int *p3)
{
if (sscanf(s, "%dx%dx%d", p1, p2, p3) != 3)
return false;
for (int n = 0; n < 3; n++) {
int s = ((int[]) { *p1, *p2, *p3 })[n];
if (s < 2 || s > 256 || ((s - 1) & s))
return false;
}
return true;
}
static int lut3d_size_valid(void *arg)
{
char *s = *(char **)arg;
int p1, p2, p3;
return parse_3dlut_size(s, &p1, &p2, &p3);
}
static int backend_valid(void *arg)
{
return mpgl_find_backend(*(const char **)arg) >= 0;
}
struct fbo_format {
const char *name;
GLint format;
};
const struct fbo_format fbo_formats[] = {
{"rgb", GL_RGB},
{"rgba", GL_RGBA},
{"rgb8", GL_RGB8},
{"rgb10", GL_RGB10},
{"rgb16", GL_RGB16},
{"rgb16f", GL_RGB16F},
{"rgb32f", GL_RGB32F},
{0}
};
static GLint find_fbo_format(const char *name)
{
for (const struct fbo_format *fmt = fbo_formats; fmt->name; fmt++) {
if (strcmp(fmt->name, name) == 0)
return fmt->format;
}
return -1;
}
static int fbo_format_valid(void *arg)
{
return find_fbo_format(*(const char **)arg) >= 0;
}
static bool can_use_filter_kernel(const struct filter_kernel *kernel)
{
if (!kernel)
return false;
struct filter_kernel k = *kernel;
return mp_init_filter(&k, filter_sizes, 1);
}
static const char* handle_scaler_opt(const char *name)
{
const struct filter_kernel *kernel = mp_find_filter_kernel(name);
if (can_use_filter_kernel(kernel))
return kernel->name;
for (const char **filter = fixed_scale_filters; *filter; filter++) {
if (strcmp(*filter, name) == 0)
return *filter;
}
return NULL;
}
static int scaler_valid(void *arg)
{
return handle_scaler_opt(*(const char **)arg) != NULL;
}
#if 0
static void print_scalers(void)
{
mp_msg(MSGT_VO, MSGL_INFO, "Available scalers:\n");
for (const char **e = fixed_scale_filters; *e; e++) {
mp_msg(MSGT_VO, MSGL_INFO, " %s\n", *e);
}
for (const struct filter_kernel *e = mp_filter_kernels; e->name; e++) {
if (can_use_filter_kernel(e))
mp_msg(MSGT_VO, MSGL_INFO, " %s\n", e->name);
}
}
#endif
static bool reparse_cmdline(struct gl_priv *p, char *arg)
{
struct gl_priv tmp = *p->defaults;
struct gl_priv *opt = &tmp;
if (strcmp(arg, "-") == 0) {
tmp = *p->orig_cmdline;
arg = "";
}
char *scalers[2] = {0};
char *fbo_format = NULL;
const opt_t subopts[] = {
{"srgb", OPT_ARG_BOOL, &opt->use_srgb},
{"pbo", OPT_ARG_BOOL, &opt->use_pbo},
{"glfinish", OPT_ARG_BOOL, &opt->use_glFinish},
{"swapinterval", OPT_ARG_INT, &opt->swap_interval},
{"osdcolor", OPT_ARG_INT, &opt->osd_color},
{"lscale", OPT_ARG_MSTRZ, &scalers[0], scaler_valid},
{"cscale", OPT_ARG_MSTRZ, &scalers[1], scaler_valid},
{"lparam1", OPT_ARG_FLOAT, &opt->scaler_params[0]},
{"lparam2", OPT_ARG_FLOAT, &opt->scaler_params[1]},
{"fancy-downscaling", OPT_ARG_BOOL, &opt->use_fancy_downscaling},
{"indirect", OPT_ARG_BOOL, &opt->use_indirect},
{"scale-sep", OPT_ARG_BOOL, &opt->use_scale_sep},
{"fbo-format", OPT_ARG_MSTRZ, &fbo_format, fbo_format_valid},
{"dither-depth", OPT_ARG_INT, &opt->dither_depth},
{NULL}
};
if (subopt_parse(arg, subopts) != 0)
return false;
p->fbo_format = opt->fbo_format;
if (fbo_format)
p->fbo_format = find_fbo_format(fbo_format);
free(fbo_format);
for (int n = 0; n < 2; n++) {
p->scalers[n].name = opt->scalers[n].name;
if (scalers[n])
p->scalers[n].name = handle_scaler_opt(scalers[n]);
free(scalers[n]);
}
// xxx ideally we'd put all options into an option struct, and just copy
p->use_srgb = opt->use_srgb; //xxx changing srgb will be wrong on RGB input!
p->use_pbo = opt->use_pbo;
p->use_glFinish = opt->use_glFinish;
p->swap_interval = opt->swap_interval;
p->osd_color = opt->osd_color;
memcpy(p->scaler_params, opt->scaler_params, sizeof(p->scaler_params));
p->use_fancy_downscaling = opt->use_fancy_downscaling;
p->use_indirect = opt->use_indirect;
p->use_scale_sep = opt->use_scale_sep;
p->dither_depth = opt->dither_depth;
check_gl_features(p);
return true;
}
static int preinit(struct vo *vo, const char *arg)
{
struct gl_priv *p = talloc_zero(vo, struct gl_priv);
vo->priv = p;
bool hq = strcmp(vo->driver->info->short_name, "opengl-hq") == 0;
*p = (struct gl_priv) {
.vo = vo,
.colorspace = MP_CSP_DETAILS_DEFAULTS,
.use_npot = 1,
.use_pbo = hq,
.swap_interval = vo_vsync,
.osd_color = 0xffffff,
.dither_depth = hq ? 0 : -1,
.fbo_format = hq ? GL_RGB16 : GL_RGB,
.use_scale_sep = 1,
.use_fancy_downscaling = hq,
.scalers = {
{ .index = 0, .name = hq ? "lanczos2" : "bilinear" },
{ .index = 1, .name = "bilinear" },
},
.scaler_params = {NAN, NAN},
};
p->defaults = talloc(p, struct gl_priv);
*p->defaults = *p;
char *scalers[2] = {0};
char *backend_arg = NULL;
char *fbo_format = NULL;
char *icc_profile = NULL;
char *icc_cache = NULL;
int icc_intent = -1;
char *icc_size_str = NULL;
const opt_t subopts[] = {
{"gamma", OPT_ARG_BOOL, &p->use_gamma},
{"srgb", OPT_ARG_BOOL, &p->use_srgb},
{"npot", OPT_ARG_BOOL, &p->use_npot},
{"pbo", OPT_ARG_BOOL, &p->use_pbo},
{"glfinish", OPT_ARG_BOOL, &p->use_glFinish},
{"swapinterval", OPT_ARG_INT, &p->swap_interval},
{"osdcolor", OPT_ARG_INT, &p->osd_color},
{"stereo", OPT_ARG_INT, &p->stereo_mode},
{"lscale", OPT_ARG_MSTRZ, &scalers[0], scaler_valid},
{"cscale", OPT_ARG_MSTRZ, &scalers[1], scaler_valid},
{"lparam1", OPT_ARG_FLOAT, &p->scaler_params[0]},
{"lparam2", OPT_ARG_FLOAT, &p->scaler_params[1]},
{"fancy-downscaling", OPT_ARG_BOOL, &p->use_fancy_downscaling},
{"debug", OPT_ARG_BOOL, &p->use_gl_debug},
{"indirect", OPT_ARG_BOOL, &p->use_indirect},
{"scale-sep", OPT_ARG_BOOL, &p->use_scale_sep},
{"fbo-format", OPT_ARG_MSTRZ, &fbo_format, fbo_format_valid},
{"backend", OPT_ARG_MSTRZ, &backend_arg, backend_valid},
{"sw", OPT_ARG_BOOL, &p->allow_sw},
{"icc-profile", OPT_ARG_MSTRZ, &icc_profile},
{"icc-cache", OPT_ARG_MSTRZ, &icc_cache},
{"icc-intent", OPT_ARG_INT, &icc_intent},
{"3dlut-size", OPT_ARG_MSTRZ, &icc_size_str,
lut3d_size_valid},
{"dither-depth", OPT_ARG_INT, &p->dither_depth},
{NULL}
};
if (subopt_parse(arg, subopts) != 0) {
mp_msg(MSGT_VO, MSGL_FATAL, help_text);
goto err_out;
}
int backend = backend_arg ? mpgl_find_backend(backend_arg) : GLTYPE_AUTO;
free(backend_arg);
if (fbo_format)
p->fbo_format = find_fbo_format(fbo_format);
free(fbo_format);
for (int n = 0; n < 2; n++) {
if (scalers[n])
p->scalers[n].name = handle_scaler_opt(scalers[n]);
free(scalers[n]);
}
int s_r = 128, s_g = 256, s_b = 64;
if (icc_size_str)
parse_3dlut_size(icc_size_str, &s_r, &s_g, &s_b);
free(icc_size_str);
bool success = true;
if (icc_profile) {
success = load_icc(p, icc_profile, icc_cache, icc_intent,
s_r, s_g, s_b);
}
free(icc_profile);
free(icc_cache);
if (!success)
goto err_out;
p->orig_cmdline = talloc(p, struct gl_priv);
*p->orig_cmdline = *p;
p->eosd = eosd_packer_create(vo);
p->glctx = mpgl_init(backend, vo);
if (!p->glctx)
goto err_out;
p->gl = p->glctx->gl;
if (!create_window(p, 320, 200, VOFLAG_HIDDEN))
goto err_out;
check_gl_features(p);
// We created a window to test whether the GL context could be
// created and so on. Destroy that window to make sure all state
// associated with it is lost.
uninit_gl(p);
if (!mpgl_destroy_window(p->glctx))
goto err_out;
return 0;
err_out:
uninit(vo);
return -1;
}
const struct vo_driver video_out_opengl = {
.is_new = true,
.info = &(const vo_info_t) {
"Extended OpenGL Renderer",
"opengl",
"Based on vo_gl.c by Reimar Doeffinger",
""
},
.preinit = preinit,
.config = config,
.control = control,
.draw_slice = draw_slice,
.draw_osd = draw_osd,
.flip_page = flip_page,
.check_events = check_events,
.uninit = uninit,
};
const struct vo_driver video_out_opengl_hq = {
.is_new = true,
.info = &(const vo_info_t) {
"Extended OpenGL Renderer (high quality rendering preset)",
"opengl-hq",
"Based on vo_gl.c by Reimar Doeffinger",
""
},
.preinit = preinit,
.config = config,
.control = control,
.draw_slice = draw_slice,
.draw_osd = draw_osd,
.flip_page = flip_page,
.check_events = check_events,
.uninit = uninit,
};
static const char help_text[] =
"\n--vo=opengl command line help:\n"
"Example: mpv --vo=opengl:scale-sep:lscale=lanczos2\n"
"\nOptions:\n"
" lscale=<filter>\n"
" Set the scaling filter. Possible choices:\n"
" bilinear: bilinear texture filtering (fastest).\n"
" bicubic_fast: bicubic filter (without lookup texture).\n"
" sharpen3: unsharp masking (sharpening) with radius=3.\n"
" sharpen5: unsharp masking (sharpening) with radius=5.\n"
" lanczos2: Lanczos with radius=2 (recommended).\n"
" lanczos3: Lanczos with radius=3 (not recommended).\n"
" mitchell: Mitchell-Netravali.\n"
" Default: bilinear\n"
" lparam1=<value> / lparam2=<value>\n"
" Set parameters for configurable filters. Affects chroma scaler\n"
" as well.\n"
" Filters which use this:\n"
" mitchell: b and c params (defaults: b=1/3 c=1/3)\n"
" kaiser: (defaults: 6.33 6.33)\n"
" sharpen3: lparam1 sets sharpening strength (default: 0.5)\n"
" sharpen5: as with sharpen3\n"
" osdcolor=<0xAARRGGBB>\n"
" Use the given color for the OSD.\n"
" stereo=<n>\n"
" 0: normal display\n"
" 1: side-by-side to red-cyan stereo\n"
" 2: side-by-side to green-magenta stereo\n"
" 3: side-by-side to quadbuffer stereo\n"
" srgb\n"
" Enable gamma-correct scaling by working in linear light. This\n"
" makes use of sRGB textures and framebuffers.\n"
" This option forces the options 'indirect' and 'gamma'.\n"
" NOTE: For YUV colorspaces, gamma 2.2 is assumed. RGB input is always\n"
" assumed to be in sRGB.\n"
" pbo\n"
" Enable use of PBOs. This is faster, but can sometimes lead to\n"
" sporadic and temporary image corruption.\n"
" dither-depth=<n>\n"
" Positive non-zero values select the target bit depth.\n"
" -1: Disable any dithering done by mpv.\n"
" 0: Automatic selection. If output bit depth can't be detected,\n"
" 8 bits per component are assumed.\n"
" 8: Dither to 8 bit output.\n"
" Default: -1.\n"
" Note that dithering will always be disabled if the bit depth\n"
" of the video is lower or qual to the detected dither-depth.\n"
" If color management is enabled, input depth is assumed to be\n"
" 16 bits, because the 3D LUT output is 16 bit wide.\n"
" debug\n"
" Check for OpenGL errors, i.e. call glGetError(). Also request a\n"
" debug OpenGL context.\n"
"Less useful options:\n"
" swapinterval=<n>\n"
" Interval in displayed frames between to buffer swaps.\n"
" 1 is equivalent to enable VSYNC, 0 to disable VSYNC.\n"
" no-scale-sep\n"
" When using a separable scale filter for luma, usually two filter\n"
" passes are done. This is often faster. However, it forces\n"
" conversion to RGB in an extra pass, so it can actually be slower\n"
" if used with fast filters on small screen resolutions. Using\n"
" this options will make rendering a single operation.\n"
" Note that chroma scalers are always done as 1-pass filters.\n"
" cscale=<n>\n"
" As lscale but for chroma (2x slower with little visible effect).\n"
" Note that with some scaling filters, upscaling is always done in\n"
" RGB. If chroma is not subsampled, this option is ignored, and the\n"
" luma scaler is used instead. Setting this option is often useless.\n"
" fancy-downscaling\n"
" When using convolution based filters, extend the filter size\n"
" when downscaling. Trades quality for reduced downscaling performance.\n"
" no-npot\n"
" Force use of power-of-2 texture sizes. For debugging only.\n"
" Borders will look discolored due to filtering.\n"
" glfinish\n"
" Call glFinish() before swapping buffers\n"
" backend=<sys>\n"
" auto: auto-select (default)\n"
" cocoa: Cocoa/OSX\n"
" win: Win32/WGL\n"
" x11: X11/GLX\n"
" indirect\n"
" Do YUV conversion and scaling as separate passes. This will\n"
" first render the video into a video-sized RGB texture, and\n"
" draw the result on screen. The luma scaler is used to scale\n"
" the RGB image when rendering to screen. The chroma scaler\n"
" is used only on YUV conversion, and only if the video uses\n"
" chroma-subsampling.\n"
" This mechanism is disabled on RGB input.\n"
" fbo-format=<fmt>\n"
" Selects the internal format of any FBO textures used.\n"
" fmt can be one of: rgb, rgba, rgb8, rgb10, rgb16, rgb16f, rgb32f\n"
" Default: rgb.\n"
" gamma\n"
" Always enable gamma control. (Disables delayed enabling.)\n"
"Color management:\n"
" icc-profile=<file>\n"
" Load an ICC profile and use it to transform linear RGB to\n"
" screen output. Needs LittleCMS2 support compiled in.\n"
" icc-cache=<file>\n"
" Store and load the 3D LUT created from the ICC profile in\n"
" this file. This can be used to speed up loading, since\n"
" LittleCMS2 can take a while to create the 3D LUT.\n"
" Note that this file will be up to ~100 MB big.\n"
" icc-intent=<value>\n"
" 0: perceptual\n"
" 1: relative colorimetric\n"
" 2: saturation\n"
" 3: absolute colorimetric (default)\n"
" 3dlut-size=<r>x<g>x<b>\n"
" Size of the 3D LUT generated from the ICC profile in each\n"
" dimension. Default is 128x256x64.\n"
" Sizes must be a power of two, and 256 at most.\n"
"Note: all defaults mentioned are for 'opengl', not 'opengl-hq'.\n"
"\n";