mirror of https://github.com/mpv-player/mpv
vo_opengl: add support for custom shaders
This commit is contained in:
parent
bf4dd877e9
commit
4d6b9550fe
|
@ -342,6 +342,9 @@ Available video output drivers are:
|
||||||
exchange for adding some blur. This filter is good at temporal
|
exchange for adding some blur. This filter is good at temporal
|
||||||
interpolation, and also known as "smoothmotion" (see ``tscale``).
|
interpolation, and also known as "smoothmotion" (see ``tscale``).
|
||||||
|
|
||||||
|
``custom``
|
||||||
|
A user-defined custom shader (see ``scale-shader``).
|
||||||
|
|
||||||
There are some more filters, but most are not as useful. For a complete
|
There are some more filters, but most are not as useful. For a complete
|
||||||
list, pass ``help`` as value, e.g.::
|
list, pass ``help`` as value, e.g.::
|
||||||
|
|
||||||
|
@ -525,6 +528,70 @@ Available video output drivers are:
|
||||||
feature doesn't work correctly with different scale factors in
|
feature doesn't work correctly with different scale factors in
|
||||||
different directions.
|
different directions.
|
||||||
|
|
||||||
|
``source-shader=<file>``, ``scale-shader=<file>``, ``pre-shaders=<files>``, ``post-shaders=<files>``
|
||||||
|
Custom GLSL fragment shaders.
|
||||||
|
|
||||||
|
source-shader
|
||||||
|
This gets applied directly onto the source planes, before
|
||||||
|
any sort of upscaling or conversion whatsoever. For YCbCr content,
|
||||||
|
this means it gets applied on the luma and chroma planes
|
||||||
|
separately. In general, this shader shouldn't be making any
|
||||||
|
assumptions about the colorspace. It could be RGB, YCbCr, XYZ or
|
||||||
|
something else entirely. It's used purely for fixing numerical
|
||||||
|
quirks of the input, eg. debanding or deblocking.
|
||||||
|
pre-shaders (list)
|
||||||
|
These get applied after conversion to RGB and before linearization
|
||||||
|
and upscaling. Operates on non-linear RGB (same as input). This is
|
||||||
|
the best place to put things like sharpen filters.
|
||||||
|
scale-shader
|
||||||
|
This gets used instead of scale/cscale when those options are set
|
||||||
|
to ``custom``. The colorspace it operates on depends on the values
|
||||||
|
of ``linear-scaling`` and ``sigmoid-upscaling``, so no assumptions
|
||||||
|
should be made here.
|
||||||
|
post-shaders (list)
|
||||||
|
These get applied after upscaling and subtitle blending (when
|
||||||
|
``blend-subtitles`` is enabled), but before color management.
|
||||||
|
Operates on linear RGB if ``linear-scaling`` is in effect,
|
||||||
|
otherwise non-linear RGB. This is the best place for colorspace
|
||||||
|
transformations (eg. saturation mapping).
|
||||||
|
|
||||||
|
These files must define a function with the following signature::
|
||||||
|
|
||||||
|
vec4 sample(sampler2D tex, vec2 pos, vec2 tex_size)
|
||||||
|
|
||||||
|
The meanings of the parameters are as follows:
|
||||||
|
|
||||||
|
sampler2D tex
|
||||||
|
The source texture for the shader.
|
||||||
|
vec2 pos
|
||||||
|
The position to be sampled, in coordinate space [0-1].
|
||||||
|
vec2 tex_size
|
||||||
|
The size of the texture, in pixels. This may differ from image_size,
|
||||||
|
eg. for subsampled content or for post-shaders.
|
||||||
|
|
||||||
|
In addition to these parameters, the following uniforms are also
|
||||||
|
globally available:
|
||||||
|
|
||||||
|
float random
|
||||||
|
A random number in the range [0-1], different per frame.
|
||||||
|
int frame
|
||||||
|
A simple count of frames rendered, increases by one per frame and
|
||||||
|
never resets (regardless of seeks).
|
||||||
|
vec2 image_size
|
||||||
|
The size in pixels of the input image.
|
||||||
|
float cmul (source-shader only)
|
||||||
|
The multiplier needed to pull colors up to the right bit depth. The
|
||||||
|
source-shader must multiply any sampled colors by this, in order
|
||||||
|
to normalize them to the full scale.
|
||||||
|
|
||||||
|
For example, a shader that inverts the colors could look like this::
|
||||||
|
|
||||||
|
vec4 sample(sampler2D tex, vec2 pos, vec2 tex_size)
|
||||||
|
{
|
||||||
|
vec4 color = texture(tex, pos);
|
||||||
|
return vec4(1.0 - color.rgb, color.a);
|
||||||
|
}
|
||||||
|
|
||||||
``sigmoid-upscaling``
|
``sigmoid-upscaling``
|
||||||
When upscaling, use a sigmoidal color transform to avoid emphasizing
|
When upscaling, use a sigmoidal color transform to avoid emphasizing
|
||||||
ringing artifacts. This also enables ``linear-scaling``.
|
ringing artifacts. This also enables ``linear-scaling``.
|
||||||
|
@ -705,8 +772,9 @@ Available video output drivers are:
|
||||||
Blend subtitles directly onto upscaled video frames, before
|
Blend subtitles directly onto upscaled video frames, before
|
||||||
interpolation and/or color management (default: no). Enabling this
|
interpolation and/or color management (default: no). Enabling this
|
||||||
causes subtitles to be affected by ``icc-profile``, ``target-prim``,
|
causes subtitles to be affected by ``icc-profile``, ``target-prim``,
|
||||||
``target-trc``, ``interpolation``, ``gamma`` and ``linear-scaling``.
|
``target-trc``, ``interpolation``, ``gamma``, ``post-shader`` and
|
||||||
It also increases subtitle performance when using ``interpolation``.
|
``linear-scaling``. It also increases subtitle performance when using
|
||||||
|
``interpolation``.
|
||||||
|
|
||||||
The downside of enabling this is that it restricts subtitles to the
|
The downside of enabling this is that it restricts subtitles to the
|
||||||
visible portion of the video, so you can't have subtitles exist in the
|
visible portion of the video, so you can't have subtitles exist in the
|
||||||
|
|
|
@ -929,6 +929,20 @@ struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
|
||||||
return (struct bstr){buf, total_read};
|
return (struct bstr){buf, total_read};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct bstr stream_read_file(const char *filename, void *talloc_ctx,
|
||||||
|
struct mpv_global *global, int max_size)
|
||||||
|
{
|
||||||
|
struct bstr res = {0};
|
||||||
|
char *fname = mp_get_user_path(NULL, global, filename);
|
||||||
|
stream_t *s = stream_open(fname, global);
|
||||||
|
if (s) {
|
||||||
|
res = stream_read_complete(s, talloc_ctx, max_size);
|
||||||
|
free_stream(s);
|
||||||
|
}
|
||||||
|
talloc_free(fname);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
struct mp_cancel {
|
struct mp_cancel {
|
||||||
atomic_bool triggered;
|
atomic_bool triggered;
|
||||||
#ifdef __MINGW32__
|
#ifdef __MINGW32__
|
||||||
|
|
|
@ -258,6 +258,8 @@ struct mpv_global;
|
||||||
|
|
||||||
struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
|
struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
|
||||||
int max_size);
|
int max_size);
|
||||||
|
struct bstr stream_read_file(const char *filename, void *talloc_ctx,
|
||||||
|
struct mpv_global *global, int max_size);
|
||||||
int stream_control(stream_t *s, int cmd, void *arg);
|
int stream_control(stream_t *s, int cmd, void *arg);
|
||||||
void free_stream(stream_t *s);
|
void free_stream(stream_t *s);
|
||||||
struct stream *stream_create(const char *url, int flags,
|
struct stream *stream_create(const char *url, int flags,
|
||||||
|
|
|
@ -102,18 +102,6 @@ static void lcms2_error_handler(cmsContext ctx, cmsUInt32Number code,
|
||||||
MP_ERR(p, "lcms2: %s\n", msg);
|
MP_ERR(p, "lcms2: %s\n", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct bstr load_file(void *talloc_ctx, const char *filename,
|
|
||||||
struct mpv_global *global)
|
|
||||||
{
|
|
||||||
struct bstr res = {0};
|
|
||||||
stream_t *s = stream_open(filename, global);
|
|
||||||
if (s) {
|
|
||||||
res = stream_read_complete(s, talloc_ctx, 1000000000);
|
|
||||||
free_stream(s);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool load_profile(struct gl_lcms *p)
|
static bool load_profile(struct gl_lcms *p)
|
||||||
{
|
{
|
||||||
if (p->icc_data && p->icc_size)
|
if (p->icc_data && p->icc_size)
|
||||||
|
@ -124,7 +112,8 @@ static bool load_profile(struct gl_lcms *p)
|
||||||
|
|
||||||
char *fname = mp_get_user_path(NULL, p->global, p->icc_path);
|
char *fname = mp_get_user_path(NULL, p->global, p->icc_path);
|
||||||
MP_VERBOSE(p, "Opening ICC profile '%s'\n", fname);
|
MP_VERBOSE(p, "Opening ICC profile '%s'\n", fname);
|
||||||
struct bstr iccdata = load_file(p, fname, p->global);
|
struct bstr iccdata = stream_read_file(fname, p, p->global,
|
||||||
|
100000000); // 100 MB
|
||||||
talloc_free(fname);
|
talloc_free(fname);
|
||||||
if (!iccdata.len)
|
if (!iccdata.len)
|
||||||
return false;
|
return false;
|
||||||
|
@ -238,7 +227,8 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d)
|
||||||
// check cache
|
// check cache
|
||||||
if (cache_file) {
|
if (cache_file) {
|
||||||
MP_VERBOSE(p, "Opening 3D LUT cache in file '%s'.\n", cache_file);
|
MP_VERBOSE(p, "Opening 3D LUT cache in file '%s'.\n", cache_file);
|
||||||
struct bstr cachedata = load_file(tmp, cache_file, p->global);
|
struct bstr cachedata = stream_read_file(cache_file, tmp, p->global,
|
||||||
|
1000000000); // 1 GB
|
||||||
if (cachedata.len == talloc_get_size(output)) {
|
if (cachedata.len == talloc_get_size(output)) {
|
||||||
memcpy(output, cachedata.start, cachedata.len);
|
memcpy(output, cachedata.start, cachedata.len);
|
||||||
goto done;
|
goto done;
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "stream/stream.h"
|
||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
#include "gl_utils.h"
|
#include "gl_utils.h"
|
||||||
|
|
||||||
|
@ -464,6 +465,7 @@ void gl_set_debug_logger(GL *gl, struct mp_log *log)
|
||||||
|
|
||||||
#define SC_ENTRIES 16
|
#define SC_ENTRIES 16
|
||||||
#define SC_UNIFORM_ENTRIES 20
|
#define SC_UNIFORM_ENTRIES 20
|
||||||
|
#define SC_FILE_ENTRIES 10
|
||||||
|
|
||||||
enum uniform_type {
|
enum uniform_type {
|
||||||
UT_invalid,
|
UT_invalid,
|
||||||
|
@ -484,6 +486,11 @@ struct sc_uniform {
|
||||||
} v;
|
} v;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct sc_file {
|
||||||
|
char *path;
|
||||||
|
char *body;
|
||||||
|
};
|
||||||
|
|
||||||
struct sc_entry {
|
struct sc_entry {
|
||||||
GLuint gl_shader;
|
GLuint gl_shader;
|
||||||
// the following fields define the shader's contents
|
// the following fields define the shader's contents
|
||||||
|
@ -494,9 +501,11 @@ struct sc_entry {
|
||||||
struct gl_shader_cache {
|
struct gl_shader_cache {
|
||||||
GL *gl;
|
GL *gl;
|
||||||
struct mp_log *log;
|
struct mp_log *log;
|
||||||
|
struct mpv_global *global;
|
||||||
|
|
||||||
// this is modified during use (gl_sc_add() etc.)
|
// this is modified during use (gl_sc_add() etc.)
|
||||||
char *text;
|
char *text;
|
||||||
|
char *header_text;
|
||||||
struct gl_vao *vao;
|
struct gl_vao *vao;
|
||||||
|
|
||||||
struct sc_entry entries[SC_ENTRIES];
|
struct sc_entry entries[SC_ENTRIES];
|
||||||
|
@ -504,15 +513,21 @@ struct gl_shader_cache {
|
||||||
|
|
||||||
struct sc_uniform uniforms[SC_UNIFORM_ENTRIES];
|
struct sc_uniform uniforms[SC_UNIFORM_ENTRIES];
|
||||||
int num_uniforms;
|
int num_uniforms;
|
||||||
|
|
||||||
|
struct sc_file files[SC_FILE_ENTRIES];
|
||||||
|
int num_files;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log)
|
struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log,
|
||||||
|
struct mpv_global *global)
|
||||||
{
|
{
|
||||||
struct gl_shader_cache *sc = talloc_ptrtype(NULL, sc);
|
struct gl_shader_cache *sc = talloc_ptrtype(NULL, sc);
|
||||||
*sc = (struct gl_shader_cache){
|
*sc = (struct gl_shader_cache){
|
||||||
.gl = gl,
|
.gl = gl,
|
||||||
.log = log,
|
.log = log,
|
||||||
|
.global = global,
|
||||||
.text = talloc_strdup(sc, ""),
|
.text = talloc_strdup(sc, ""),
|
||||||
|
.header_text = talloc_strdup(sc, ""),
|
||||||
};
|
};
|
||||||
return sc;
|
return sc;
|
||||||
}
|
}
|
||||||
|
@ -520,6 +535,7 @@ struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log)
|
||||||
void gl_sc_reset(struct gl_shader_cache *sc)
|
void gl_sc_reset(struct gl_shader_cache *sc)
|
||||||
{
|
{
|
||||||
sc->text[0] = '\0';
|
sc->text[0] = '\0';
|
||||||
|
sc->header_text[0] = '\0';
|
||||||
for (int n = 0; n < sc->num_uniforms; n++)
|
for (int n = 0; n < sc->num_uniforms; n++)
|
||||||
talloc_free(sc->uniforms[n].name);
|
talloc_free(sc->uniforms[n].name);
|
||||||
sc->num_uniforms = 0;
|
sc->num_uniforms = 0;
|
||||||
|
@ -555,6 +571,40 @@ void gl_sc_addf(struct gl_shader_cache *sc, const char *textf, ...)
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gl_sc_hadd(struct gl_shader_cache *sc, const char *text)
|
||||||
|
{
|
||||||
|
sc->header_text = talloc_strdup_append(sc->header_text, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *gl_sc_loadfile(struct gl_shader_cache *sc, const char *path)
|
||||||
|
{
|
||||||
|
if (!path || !path[0] || !sc->global)
|
||||||
|
return NULL;
|
||||||
|
for (int n = 0; n < sc->num_files; n++) {
|
||||||
|
if (strcmp(sc->files[n].path, path) == 0)
|
||||||
|
return sc->files[n].body;
|
||||||
|
}
|
||||||
|
// not found -> load it
|
||||||
|
if (sc->num_files == SC_FILE_ENTRIES) {
|
||||||
|
// empty cache when it overflows
|
||||||
|
for (int n = 0; n < sc->num_files; n++) {
|
||||||
|
talloc_free(sc->files[n].path);
|
||||||
|
talloc_free(sc->files[n].body);
|
||||||
|
}
|
||||||
|
sc->num_files = 0;
|
||||||
|
}
|
||||||
|
struct bstr s = stream_read_file(path, sc, sc->global, 100000); // 100 kB
|
||||||
|
if (s.len) {
|
||||||
|
struct sc_file *new = &sc->files[sc->num_files++];
|
||||||
|
*new = (struct sc_file) {
|
||||||
|
.path = talloc_strdup(NULL, path),
|
||||||
|
.body = s.start
|
||||||
|
};
|
||||||
|
return new->body;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static struct sc_uniform *find_uniform(struct gl_shader_cache *sc,
|
static struct sc_uniform *find_uniform(struct gl_shader_cache *sc,
|
||||||
const char *name)
|
const char *name)
|
||||||
{
|
{
|
||||||
|
@ -594,6 +644,15 @@ void gl_sc_uniform_f(struct gl_shader_cache *sc, char *name, GLfloat f)
|
||||||
u->v.f[0] = f;
|
u->v.f[0] = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gl_sc_uniform_i(struct gl_shader_cache *sc, char *name, GLint i)
|
||||||
|
{
|
||||||
|
struct sc_uniform *u = find_uniform(sc, name);
|
||||||
|
u->type = UT_i;
|
||||||
|
u->size = 1;
|
||||||
|
u->glsl_type = "int";
|
||||||
|
u->v.i[0] = i;
|
||||||
|
}
|
||||||
|
|
||||||
void gl_sc_uniform_vec2(struct gl_shader_cache *sc, char *name, GLfloat f[2])
|
void gl_sc_uniform_vec2(struct gl_shader_cache *sc, char *name, GLfloat f[2])
|
||||||
{
|
{
|
||||||
struct sc_uniform *u = find_uniform(sc, name);
|
struct sc_uniform *u = find_uniform(sc, name);
|
||||||
|
@ -762,6 +821,11 @@ static GLuint create_program(struct gl_shader_cache *sc, const char *vertex,
|
||||||
{
|
{
|
||||||
GL *gl = sc->gl;
|
GL *gl = sc->gl;
|
||||||
MP_VERBOSE(sc, "recompiling a shader program:\n");
|
MP_VERBOSE(sc, "recompiling a shader program:\n");
|
||||||
|
if (sc->header_text[0]) {
|
||||||
|
MP_VERBOSE(sc, "header:\n");
|
||||||
|
mp_log_source(sc->log, MSGL_V, sc->header_text);
|
||||||
|
MP_VERBOSE(sc, "body:\n");
|
||||||
|
}
|
||||||
mp_log_source(sc->log, MSGL_V, sc->text);
|
mp_log_source(sc->log, MSGL_V, sc->text);
|
||||||
GLuint prog = gl->CreateProgram();
|
GLuint prog = gl->CreateProgram();
|
||||||
compile_attach_shader(sc, prog, GL_VERTEX_SHADER, vertex);
|
compile_attach_shader(sc, prog, GL_VERTEX_SHADER, vertex);
|
||||||
|
@ -838,6 +902,12 @@ void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc)
|
||||||
struct sc_uniform *u = &sc->uniforms[n];
|
struct sc_uniform *u = &sc->uniforms[n];
|
||||||
ADD(frag, "uniform %s %s;\n", u->glsl_type, u->name);
|
ADD(frag, "uniform %s %s;\n", u->glsl_type, u->name);
|
||||||
}
|
}
|
||||||
|
// custom shader header
|
||||||
|
if (sc->header_text[0]) {
|
||||||
|
ADD(frag, "// header\n");
|
||||||
|
ADD(frag, "%s\n", sc->header_text);
|
||||||
|
ADD(frag, "// body\n");
|
||||||
|
}
|
||||||
ADD(frag, "void main() {\n");
|
ADD(frag, "void main() {\n");
|
||||||
ADD(frag, "%s", sc->text);
|
ADD(frag, "%s", sc->text);
|
||||||
// we require _all_ frag shaders to write to a "vec4 color"
|
// we require _all_ frag shaders to write to a "vec4 color"
|
||||||
|
|
|
@ -119,13 +119,17 @@ void gl_set_debug_logger(GL *gl, struct mp_log *log);
|
||||||
|
|
||||||
struct gl_shader_cache;
|
struct gl_shader_cache;
|
||||||
|
|
||||||
struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log);
|
struct gl_shader_cache *gl_sc_create(GL *gl, struct mp_log *log,
|
||||||
|
struct mpv_global *global);
|
||||||
void gl_sc_destroy(struct gl_shader_cache *sc);
|
void gl_sc_destroy(struct gl_shader_cache *sc);
|
||||||
void gl_sc_add(struct gl_shader_cache *sc, const char *text);
|
void gl_sc_add(struct gl_shader_cache *sc, const char *text);
|
||||||
void gl_sc_addf(struct gl_shader_cache *sc, const char *textf, ...);
|
void gl_sc_addf(struct gl_shader_cache *sc, const char *textf, ...);
|
||||||
|
void gl_sc_hadd(struct gl_shader_cache *sc, const char *text);
|
||||||
|
const char *gl_sc_loadfile(struct gl_shader_cache *sc, const char *path);
|
||||||
void gl_sc_uniform_sampler(struct gl_shader_cache *sc, char *name, GLenum target,
|
void gl_sc_uniform_sampler(struct gl_shader_cache *sc, char *name, GLenum target,
|
||||||
int unit);
|
int unit);
|
||||||
void gl_sc_uniform_f(struct gl_shader_cache *sc, char *name, GLfloat f);
|
void gl_sc_uniform_f(struct gl_shader_cache *sc, char *name, GLfloat f);
|
||||||
|
void gl_sc_uniform_i(struct gl_shader_cache *sc, char *name, GLint f);
|
||||||
void gl_sc_uniform_vec2(struct gl_shader_cache *sc, char *name, GLfloat f[2]);
|
void gl_sc_uniform_vec2(struct gl_shader_cache *sc, char *name, GLfloat f[2]);
|
||||||
void gl_sc_uniform_vec3(struct gl_shader_cache *sc, char *name, GLfloat f[3]);
|
void gl_sc_uniform_vec3(struct gl_shader_cache *sc, char *name, GLfloat f[3]);
|
||||||
void gl_sc_uniform_mat2(struct gl_shader_cache *sc, char *name,
|
void gl_sc_uniform_mat2(struct gl_shader_cache *sc, char *name,
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <libavutil/common.h>
|
#include <libavutil/common.h>
|
||||||
|
#include <libavutil/lfg.h>
|
||||||
|
|
||||||
#include "gl_video.h"
|
#include "gl_video.h"
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ static const char *const fixed_scale_filters[] = {
|
||||||
"sharpen3",
|
"sharpen3",
|
||||||
"sharpen5",
|
"sharpen5",
|
||||||
"oversample",
|
"oversample",
|
||||||
|
"custom",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
static const char *const fixed_tscale_filters[] = {
|
static const char *const fixed_tscale_filters[] = {
|
||||||
|
@ -144,6 +146,7 @@ struct src_tex {
|
||||||
struct gl_video {
|
struct gl_video {
|
||||||
GL *gl;
|
GL *gl;
|
||||||
|
|
||||||
|
struct mpv_global *global;
|
||||||
struct mp_log *log;
|
struct mp_log *log;
|
||||||
struct gl_video_opts opts;
|
struct gl_video_opts opts;
|
||||||
bool gl_debug;
|
bool gl_debug;
|
||||||
|
@ -179,11 +182,17 @@ struct gl_video {
|
||||||
|
|
||||||
struct video_image image;
|
struct video_image image;
|
||||||
|
|
||||||
struct fbotex indirect_fbo; // RGB target
|
|
||||||
struct fbotex chroma_merge_fbo;
|
struct fbotex chroma_merge_fbo;
|
||||||
|
struct fbotex source_fbo;
|
||||||
|
struct fbotex indirect_fbo;
|
||||||
struct fbotex blend_subs_fbo;
|
struct fbotex blend_subs_fbo;
|
||||||
struct fbosurface surfaces[FBOSURFACES_MAX];
|
struct fbosurface surfaces[FBOSURFACES_MAX];
|
||||||
|
|
||||||
|
// these are duplicated so we can keep rendering back and forth between
|
||||||
|
// them to support an unlimited number of shader passes per step
|
||||||
|
struct fbotex pre_fbo[2];
|
||||||
|
struct fbotex post_fbo[2];
|
||||||
|
|
||||||
int surface_idx;
|
int surface_idx;
|
||||||
int surface_now;
|
int surface_now;
|
||||||
bool is_interpolated;
|
bool is_interpolated;
|
||||||
|
@ -202,10 +211,13 @@ struct gl_video {
|
||||||
struct src_tex pass_tex[TEXUNIT_VIDEO_NUM];
|
struct src_tex pass_tex[TEXUNIT_VIDEO_NUM];
|
||||||
bool use_indirect;
|
bool use_indirect;
|
||||||
bool use_linear;
|
bool use_linear;
|
||||||
|
bool use_full_range;
|
||||||
float user_gamma;
|
float user_gamma;
|
||||||
struct fbotex copy_fbos[4];
|
struct fbotex copy_fbos[4];
|
||||||
|
|
||||||
|
int frames_uploaded;
|
||||||
int frames_rendered;
|
int frames_rendered;
|
||||||
|
AVLFG lfg;
|
||||||
|
|
||||||
// Cached because computing it can take relatively long
|
// Cached because computing it can take relatively long
|
||||||
int last_dither_matrix_size;
|
int last_dither_matrix_size;
|
||||||
|
@ -437,6 +449,10 @@ const struct m_sub_options gl_video_conf = {
|
||||||
({"no", 0},
|
({"no", 0},
|
||||||
{"yes", 1},
|
{"yes", 1},
|
||||||
{"video", 2})),
|
{"video", 2})),
|
||||||
|
OPT_STRING("source-shader", source_shader, 0),
|
||||||
|
OPT_STRING("scale-shader", scale_shader, 0),
|
||||||
|
OPT_STRINGLIST("pre-shaders", pre_shaders, 0),
|
||||||
|
OPT_STRINGLIST("post-shaders", post_shaders, 0),
|
||||||
|
|
||||||
OPT_REMOVED("approx-gamma", "this is always enabled now"),
|
OPT_REMOVED("approx-gamma", "this is always enabled now"),
|
||||||
OPT_REMOVED("cscale-down", "chroma is never downscaled"),
|
OPT_REMOVED("cscale-down", "chroma is never downscaled"),
|
||||||
|
@ -551,10 +567,16 @@ static void uninit_rendering(struct gl_video *p)
|
||||||
gl->DeleteTextures(1, &p->dither_texture);
|
gl->DeleteTextures(1, &p->dither_texture);
|
||||||
p->dither_texture = 0;
|
p->dither_texture = 0;
|
||||||
|
|
||||||
fbotex_uninit(&p->indirect_fbo);
|
|
||||||
fbotex_uninit(&p->chroma_merge_fbo);
|
fbotex_uninit(&p->chroma_merge_fbo);
|
||||||
|
fbotex_uninit(&p->source_fbo);
|
||||||
|
fbotex_uninit(&p->indirect_fbo);
|
||||||
fbotex_uninit(&p->blend_subs_fbo);
|
fbotex_uninit(&p->blend_subs_fbo);
|
||||||
|
|
||||||
|
for (int n = 0; n < 2; n++) {
|
||||||
|
fbotex_uninit(&p->pre_fbo[n]);
|
||||||
|
fbotex_uninit(&p->post_fbo[n]);
|
||||||
|
}
|
||||||
|
|
||||||
for (int n = 0; n < FBOSURFACES_MAX; n++)
|
for (int n = 0; n < FBOSURFACES_MAX; n++)
|
||||||
fbotex_uninit(&p->surfaces[n].fbotex);
|
fbotex_uninit(&p->surfaces[n].fbotex);
|
||||||
|
|
||||||
|
@ -691,6 +713,8 @@ static void init_video(struct gl_video *p)
|
||||||
eq_caps |= MP_CSP_EQ_CAPS_BRIGHTNESS;
|
eq_caps |= MP_CSP_EQ_CAPS_BRIGHTNESS;
|
||||||
p->video_eq.capabilities = eq_caps;
|
p->video_eq.capabilities = eq_caps;
|
||||||
|
|
||||||
|
av_lfg_init(&p->lfg, 1);
|
||||||
|
|
||||||
debug_check_gl(p, "before video texture creation");
|
debug_check_gl(p, "before video texture creation");
|
||||||
|
|
||||||
struct video_image *vimg = &p->image;
|
struct video_image *vimg = &p->image;
|
||||||
|
@ -874,6 +898,39 @@ static void uninit_scaler(struct gl_video *p, struct scaler *scaler)
|
||||||
scaler->initialized = false;
|
scaler->initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void load_shader(struct gl_video *p, const char *body)
|
||||||
|
{
|
||||||
|
gl_sc_hadd(p->sc, body);
|
||||||
|
gl_sc_uniform_f(p->sc, "random", (double)av_lfg_get(&p->lfg) / UINT32_MAX);
|
||||||
|
gl_sc_uniform_f(p->sc, "frame", p->frames_uploaded);
|
||||||
|
gl_sc_uniform_vec2(p->sc, "image_size", (GLfloat[]){p->image_w, p->image_h});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies an arbitrary number of shaders in sequence, using the given pair
|
||||||
|
// of FBOs as intermediate buffers. Returns whether any shaders were applied.
|
||||||
|
static bool apply_shaders(struct gl_video *p, char **shaders,
|
||||||
|
struct fbotex textures[2], int tex_num,
|
||||||
|
int tex_w, int tex_h)
|
||||||
|
{
|
||||||
|
if (!shaders)
|
||||||
|
return false;
|
||||||
|
bool success = false;
|
||||||
|
int tex = 0;
|
||||||
|
for (int n = 0; shaders[n]; n++) {
|
||||||
|
const char *body = gl_sc_loadfile(p->sc, shaders[n]);
|
||||||
|
if (!body)
|
||||||
|
continue;
|
||||||
|
finish_pass_fbo(p, &textures[tex], tex_w, tex_h, tex_num, 0);
|
||||||
|
load_shader(p, body);
|
||||||
|
GLSLF("// custom shader\n");
|
||||||
|
GLSLF("vec4 color = sample(texture%d, texcoord%d, texture_size%d);\n",
|
||||||
|
tex_num, tex_num, tex_num);
|
||||||
|
tex = (tex+1) % 2;
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
// Semantic equality
|
// Semantic equality
|
||||||
static bool double_seq(double a, double b)
|
static bool double_seq(double a, double b)
|
||||||
{
|
{
|
||||||
|
@ -1296,6 +1353,15 @@ static void pass_sample(struct gl_video *p, int src_tex, struct scaler *scaler,
|
||||||
pass_sample_sharpen5(p, scaler);
|
pass_sample_sharpen5(p, scaler);
|
||||||
} else if (strcmp(name, "oversample") == 0) {
|
} else if (strcmp(name, "oversample") == 0) {
|
||||||
pass_sample_oversample(p, scaler, w, h);
|
pass_sample_oversample(p, scaler, w, h);
|
||||||
|
} else if (strcmp(name, "custom") == 0) {
|
||||||
|
const char *body = gl_sc_loadfile(p->sc, p->opts.scale_shader);
|
||||||
|
if (body) {
|
||||||
|
load_shader(p, body);
|
||||||
|
GLSLF("// custom scale-shader\n");
|
||||||
|
GLSL(vec4 color = sample(tex, pos, size);)
|
||||||
|
} else {
|
||||||
|
p->opts.scale_shader = NULL;
|
||||||
|
}
|
||||||
} else if (scaler->kernel && scaler->kernel->polar) {
|
} else if (scaler->kernel && scaler->kernel->polar) {
|
||||||
pass_sample_polar(p, scaler);
|
pass_sample_polar(p, scaler);
|
||||||
} else if (scaler->kernel) {
|
} else if (scaler->kernel) {
|
||||||
|
@ -1347,53 +1413,116 @@ static void pass_read_video(struct gl_video *p)
|
||||||
if (p->gl->version < 300 && p->pass_tex[0].gl_target == GL_TEXTURE_RECTANGLE)
|
if (p->gl->version < 300 && p->pass_tex[0].gl_target == GL_TEXTURE_RECTANGLE)
|
||||||
pass_copy_from_rect(p);
|
pass_copy_from_rect(p);
|
||||||
|
|
||||||
|
// The custom shader logic is a bit tricky, but there are basically three
|
||||||
|
// different places it can occur: RGB, or chroma *and* luma (which are
|
||||||
|
// treated separately even for 4:4:4 content, but the minor speed loss
|
||||||
|
// is not worth the complexity it would require).
|
||||||
|
const char *shader = gl_sc_loadfile(p->sc, p->opts.source_shader);
|
||||||
|
|
||||||
|
// Since this is before normalization, we have to take into account
|
||||||
|
// the bit depth. Specifically, we want the shader to perform normalization
|
||||||
|
// to 16 bit because otherwise it results in bad quantization, especially
|
||||||
|
// with 8-bit FBOs (where it just destroys the image completely)
|
||||||
|
int in_bits = p->image_desc.component_bits,
|
||||||
|
tx_bits = (in_bits + 7) & ~7;
|
||||||
|
float cmul = ((1 << tx_bits) - 1.0) / ((1 << in_bits) - 1.0);
|
||||||
|
// Custom source shaders are required to output at the full range
|
||||||
|
p->use_full_range = shader != NULL;
|
||||||
|
|
||||||
|
// Special case for non-planar content
|
||||||
if (p->plane_count == 1) {
|
if (p->plane_count == 1) {
|
||||||
GLSL(vec4 color = texture(texture0, texcoord0);)
|
if (shader) {
|
||||||
|
load_shader(p, shader);
|
||||||
|
GLSLF("// custom source-shader (RGB)\n");
|
||||||
|
gl_sc_uniform_f(p->sc, "cmul", cmul);
|
||||||
|
GLSL(vec4 color = sample(texture0, texcoord0, texture_size0);)
|
||||||
|
p->use_indirect = true;
|
||||||
|
} else {
|
||||||
|
GLSL(vec4 color = texture(texture0, texcoord0);)
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chroma preprocessing (merging -> shaders -> scaling)
|
||||||
|
struct src_tex luma = p->pass_tex[0];
|
||||||
|
int c_w = p->pass_tex[1].src.x1 - p->pass_tex[1].src.x0;
|
||||||
|
int c_h = p->pass_tex[1].src.y1 - p->pass_tex[1].src.y0;
|
||||||
const struct scaler_config *cscale = &p->opts.scaler[2];
|
const struct scaler_config *cscale = &p->opts.scaler[2];
|
||||||
if (p->image_desc.flags & MP_IMGFLAG_SUBSAMPLED &&
|
// Non-trivial sampling is needed on the chroma plane
|
||||||
strcmp(cscale->kernel.name, "bilinear") != 0) {
|
bool nontrivial = p->image_desc.flags & MP_IMGFLAG_SUBSAMPLED &&
|
||||||
struct src_tex luma = p->pass_tex[0];
|
strcmp(cscale->kernel.name, "bilinear") != 0;
|
||||||
if (p->plane_count > 2) {
|
|
||||||
// For simplicity and performance, we merge the chroma planes
|
bool merged = false;
|
||||||
// into a single texture before scaling, so the scaler doesn't
|
if (p->plane_count > 2 && (nontrivial || shader)) {
|
||||||
// need to run multiple times.
|
// For simplicity and performance, we merge the chroma planes
|
||||||
GLSLF("// chroma merging\n");
|
// into a single texture before scaling or shading, so the shader
|
||||||
GLSL(vec4 color = vec4(texture(texture1, texcoord1).r,
|
// doesn't need to run multiple times.
|
||||||
texture(texture2, texcoord2).r,
|
GLSLF("// chroma merging\n");
|
||||||
0.0, 1.0);)
|
GLSL(vec4 color = vec4(texture(texture1, texcoord1).r,
|
||||||
int c_w = p->pass_tex[1].src.x1 - p->pass_tex[1].src.x0;
|
texture(texture2, texcoord2).r,
|
||||||
int c_h = p->pass_tex[1].src.y1 - p->pass_tex[1].src.y0;
|
0.0, 1.0);)
|
||||||
assert(c_w == p->pass_tex[2].src.x1 - p->pass_tex[2].src.x0);
|
// We also pull up here in this case to avoid the issues described
|
||||||
assert(c_h == p->pass_tex[2].src.y1 - p->pass_tex[2].src.y0);
|
// above.
|
||||||
finish_pass_fbo(p, &p->chroma_merge_fbo, c_w, c_h, 1, 0);
|
GLSLF("color.rg *= %f;\n", cmul);
|
||||||
}
|
p->use_full_range = true;
|
||||||
|
merged = true;
|
||||||
|
assert(c_w == p->pass_tex[2].src.x1 - p->pass_tex[2].src.x0);
|
||||||
|
assert(c_h == p->pass_tex[2].src.y1 - p->pass_tex[2].src.y0);
|
||||||
|
finish_pass_fbo(p, &p->chroma_merge_fbo, c_w, c_h, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shader) {
|
||||||
|
// Chroma plane shader logic
|
||||||
|
load_shader(p, shader);
|
||||||
|
gl_sc_uniform_f(p->sc, "cmul", merged ? 1.0 : cmul);
|
||||||
|
GLSLF("// custom source-shader (chroma)\n");
|
||||||
|
GLSL(vec4 color = sample(texture1, texcoord1, texture_size1);)
|
||||||
|
GLSL(color.ba = vec2(0.0, 1.0);) // skip unused
|
||||||
|
finish_pass_fbo(p, &p->source_fbo, c_w, c_h, 1, 0);
|
||||||
|
p->use_indirect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p->image_desc.flags & MP_IMGFLAG_SUBSAMPLED && nontrivial) {
|
||||||
GLSLF("// chroma scaling\n");
|
GLSLF("// chroma scaling\n");
|
||||||
pass_sample(p, 1, &p->scaler[2], cscale, 1.0, p->image_w, p->image_h,
|
pass_sample(p, 1, &p->scaler[2], cscale, 1.0, p->image_w, p->image_h,
|
||||||
chromafix);
|
chromafix);
|
||||||
GLSL(vec2 chroma = color.rg;)
|
GLSL(vec2 chroma = color.rg;)
|
||||||
// Always force rendering to a FBO before main scaling, or we would
|
|
||||||
// scale chroma incorrectly.
|
|
||||||
p->use_indirect = true;
|
p->use_indirect = true;
|
||||||
p->pass_tex[0] = luma; // Restore luma after scaling
|
|
||||||
} else {
|
} else {
|
||||||
|
// No explicit scaling needed, either because it's trivial (ie.
|
||||||
|
// bilinear), or because there's no subsampling. We have to manually
|
||||||
|
// apply the fix to the chroma coordinates because it's not implied by
|
||||||
|
// pass_sample.
|
||||||
GLSL(vec4 color;)
|
GLSL(vec4 color;)
|
||||||
if (p->plane_count == 2) {
|
gl_transform_rect(chromafix, &p->pass_tex[1].src);
|
||||||
gl_transform_rect(chromafix, &p->pass_tex[1].src);
|
if (p->plane_count > 2 && !merged) {
|
||||||
GLSL(vec2 chroma = texture(texture1, texcoord1).rg;) // NV formats
|
|
||||||
} else {
|
|
||||||
gl_transform_rect(chromafix, &p->pass_tex[1].src);
|
|
||||||
gl_transform_rect(chromafix, &p->pass_tex[2].src);
|
gl_transform_rect(chromafix, &p->pass_tex[2].src);
|
||||||
GLSL(vec2 chroma = vec2(texture(texture1, texcoord1).r,
|
GLSL(vec2 chroma = vec2(texture(texture1, texcoord1).r,
|
||||||
texture(texture2, texcoord2).r);)
|
texture(texture2, texcoord2).r);)
|
||||||
|
} else {
|
||||||
|
GLSL(vec2 chroma = texture(texture1, texcoord1).rg;)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GLSL(color = vec4(texture(texture0, texcoord0).r, chroma, 1.0);)
|
p->pass_tex[0] = luma; // Restore the luma plane
|
||||||
if (p->has_alpha && p->plane_count >= 4)
|
if (shader) {
|
||||||
|
load_shader(p, shader);
|
||||||
|
gl_sc_uniform_f(p->sc, "cmul", cmul);
|
||||||
|
GLSLF("// custom source-shader (luma)\n");
|
||||||
|
GLSL(float luma = sample(texture0, texcoord0, texture_size0).r;)
|
||||||
|
p->use_indirect = true;
|
||||||
|
} else {
|
||||||
|
GLSL(float luma = texture(texture0, texcoord0).r;)
|
||||||
|
if (p->use_full_range)
|
||||||
|
GLSLF("luma *= %f;\n", cmul);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLSL(color = vec4(luma, chroma, 1.0);)
|
||||||
|
if (p->has_alpha && p->plane_count >= 4) {
|
||||||
GLSL(color.a = texture(texture3, texcoord3).r;)
|
GLSL(color.a = texture(texture3, texcoord3).r;)
|
||||||
|
if (p->use_full_range)
|
||||||
|
GLSLF("color.a *= %f;\n", cmul);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// yuv conversion, and any other conversions before main up/down-scaling
|
// yuv conversion, and any other conversions before main up/down-scaling
|
||||||
|
@ -1425,6 +1554,10 @@ static void pass_convert_yuv(struct gl_video *p)
|
||||||
GLSL(color.rgb = pow(color.rgb, vec3(2.6));)
|
GLSL(color.rgb = pow(color.rgb, vec3(2.6));)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Something already took care of expansion
|
||||||
|
if (p->use_full_range)
|
||||||
|
cparams.input_bits = cparams.texture_bits;
|
||||||
|
|
||||||
// Conversion from Y'CbCr or other linear spaces to RGB
|
// Conversion from Y'CbCr or other linear spaces to RGB
|
||||||
if (!p->is_rgb) {
|
if (!p->is_rgb) {
|
||||||
struct mp_cmat m = {{{0}}};
|
struct mp_cmat m = {{{0}}};
|
||||||
|
@ -1870,12 +2003,18 @@ static void pass_render_frame(struct gl_video *p)
|
||||||
GLSL(vec4 color = texture(texture0, texcoord0);)
|
GLSL(vec4 color = texture(texture0, texcoord0);)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (apply_shaders(p, p->opts.pre_shaders, &p->pre_fbo[0], 0,
|
||||||
|
p->image_w, p->image_h))
|
||||||
|
{
|
||||||
|
p->use_indirect = true;
|
||||||
|
}
|
||||||
|
|
||||||
pass_scale_main(p);
|
pass_scale_main(p);
|
||||||
|
|
||||||
|
int vp_w = p->dst_rect.x1 - p->dst_rect.x0,
|
||||||
|
vp_h = p->dst_rect.y1 - p->dst_rect.y0;
|
||||||
if (p->osd && p->opts.blend_subs == 1) {
|
if (p->osd && p->opts.blend_subs == 1) {
|
||||||
// Recreate the real video size from the src/dst rects
|
// Recreate the real video size from the src/dst rects
|
||||||
int vp_w = p->dst_rect.x1 - p->dst_rect.x0,
|
|
||||||
vp_h = p->dst_rect.y1 - p->dst_rect.y0;
|
|
||||||
struct mp_osd_res rect = {
|
struct mp_osd_res rect = {
|
||||||
.w = vp_w, .h = vp_h,
|
.w = vp_w, .h = vp_h,
|
||||||
.ml = -p->src_rect.x0, .mr = p->src_rect.x1 - p->image_w,
|
.ml = -p->src_rect.x0, .mr = p->src_rect.x1 - p->image_w,
|
||||||
|
@ -1897,6 +2036,8 @@ static void pass_render_frame(struct gl_video *p)
|
||||||
if (p->use_linear)
|
if (p->use_linear)
|
||||||
pass_linearize(p, p->image_params.gamma);
|
pass_linearize(p, p->image_params.gamma);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply_shaders(p, p->opts.post_shaders, &p->post_fbo[0], 0, vp_w, vp_h);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pass_draw_to_screen(struct gl_video *p, int fbo)
|
static void pass_draw_to_screen(struct gl_video *p, int fbo)
|
||||||
|
@ -2170,6 +2311,7 @@ static void gl_video_upload_image(struct gl_video *p)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
vimg->needs_upload = false;
|
vimg->needs_upload = false;
|
||||||
|
p->frames_uploaded++;
|
||||||
|
|
||||||
assert(mpi->num_planes == p->plane_count);
|
assert(mpi->num_planes == p->plane_count);
|
||||||
|
|
||||||
|
@ -2574,7 +2716,7 @@ void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd)
|
||||||
recreate_osd(p);
|
recreate_osd(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct gl_video *gl_video_init(GL *gl, struct mp_log *log)
|
struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g)
|
||||||
{
|
{
|
||||||
if (gl->version < 210 && gl->es < 200) {
|
if (gl->version < 210 && gl->es < 200) {
|
||||||
mp_err(log, "At least OpenGL 2.1 or OpenGL ES 2.0 required.\n");
|
mp_err(log, "At least OpenGL 2.1 or OpenGL ES 2.0 required.\n");
|
||||||
|
@ -2584,12 +2726,13 @@ struct gl_video *gl_video_init(GL *gl, struct mp_log *log)
|
||||||
struct gl_video *p = talloc_ptrtype(NULL, p);
|
struct gl_video *p = talloc_ptrtype(NULL, p);
|
||||||
*p = (struct gl_video) {
|
*p = (struct gl_video) {
|
||||||
.gl = gl,
|
.gl = gl,
|
||||||
|
.global = g,
|
||||||
.log = log,
|
.log = log,
|
||||||
.opts = gl_video_opts_def,
|
.opts = gl_video_opts_def,
|
||||||
.gl_target = GL_TEXTURE_2D,
|
.gl_target = GL_TEXTURE_2D,
|
||||||
.texture_16bit_depth = 16,
|
.texture_16bit_depth = 16,
|
||||||
.scaler = {{.index = 0}, {.index = 1}, {.index = 2}, {.index = 3}},
|
.scaler = {{.index = 0}, {.index = 1}, {.index = 2}, {.index = 3}},
|
||||||
.sc = gl_sc_create(gl, log),
|
.sc = gl_sc_create(gl, log, g),
|
||||||
};
|
};
|
||||||
gl_video_set_debug(p, true);
|
gl_video_set_debug(p, true);
|
||||||
init_gl(p);
|
init_gl(p);
|
||||||
|
|
|
@ -66,6 +66,10 @@ struct gl_video_opts {
|
||||||
struct m_color background;
|
struct m_color background;
|
||||||
int interpolation;
|
int interpolation;
|
||||||
int blend_subs;
|
int blend_subs;
|
||||||
|
char *source_shader;
|
||||||
|
char *scale_shader;
|
||||||
|
char **pre_shaders;
|
||||||
|
char **post_shaders;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct m_sub_options gl_video_conf;
|
extern const struct m_sub_options gl_video_conf;
|
||||||
|
@ -74,7 +78,7 @@ extern const struct gl_video_opts gl_video_opts_def;
|
||||||
|
|
||||||
struct gl_video;
|
struct gl_video;
|
||||||
|
|
||||||
struct gl_video *gl_video_init(GL *gl, struct mp_log *log);
|
struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g);
|
||||||
void gl_video_uninit(struct gl_video *p);
|
void gl_video_uninit(struct gl_video *p);
|
||||||
void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd);
|
void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd);
|
||||||
void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts,
|
void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts,
|
||||||
|
|
|
@ -434,7 +434,7 @@ static int preinit(struct vo *vo)
|
||||||
}
|
}
|
||||||
p->current_swap_interval = p->swap_interval;
|
p->current_swap_interval = p->swap_interval;
|
||||||
|
|
||||||
p->renderer = gl_video_init(p->gl, vo->log);
|
p->renderer = gl_video_init(p->gl, vo->log, vo->global);
|
||||||
if (!p->renderer)
|
if (!p->renderer)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
gl_video_set_osd_source(p->renderer, vo->osd);
|
gl_video_set_osd_source(p->renderer, vo->osd);
|
||||||
|
|
|
@ -233,7 +233,7 @@ int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts,
|
||||||
|
|
||||||
mpgl_load_functions2(ctx->gl, get_proc_address, get_proc_address_ctx,
|
mpgl_load_functions2(ctx->gl, get_proc_address, get_proc_address_ctx,
|
||||||
exts, ctx->log);
|
exts, ctx->log);
|
||||||
ctx->renderer = gl_video_init(ctx->gl, ctx->log);
|
ctx->renderer = gl_video_init(ctx->gl, ctx->log, NULL);
|
||||||
if (!ctx->renderer)
|
if (!ctx->renderer)
|
||||||
return MPV_ERROR_UNSUPPORTED;
|
return MPV_ERROR_UNSUPPORTED;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue