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
|
||||
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
|
||||
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
|
||||
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``
|
||||
When upscaling, use a sigmoidal color transform to avoid emphasizing
|
||||
ringing artifacts. This also enables ``linear-scaling``.
|
||||
|
@ -705,8 +772,9 @@ Available video output drivers are:
|
|||
Blend subtitles directly onto upscaled video frames, before
|
||||
interpolation and/or color management (default: no). Enabling this
|
||||
causes subtitles to be affected by ``icc-profile``, ``target-prim``,
|
||||
``target-trc``, ``interpolation``, ``gamma`` and ``linear-scaling``.
|
||||
It also increases subtitle performance when using ``interpolation``.
|
||||
``target-trc``, ``interpolation``, ``gamma``, ``post-shader`` and
|
||||
``linear-scaling``. It also increases subtitle performance when using
|
||||
``interpolation``.
|
||||
|
||||
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
|
||||
|
|
|
@ -929,6 +929,20 @@ struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
|
|||
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 {
|
||||
atomic_bool triggered;
|
||||
#ifdef __MINGW32__
|
||||
|
|
|
@ -258,6 +258,8 @@ struct mpv_global;
|
|||
|
||||
struct bstr stream_read_complete(struct stream *s, void *talloc_ctx,
|
||||
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);
|
||||
void free_stream(stream_t *s);
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
if (!iccdata.len)
|
||||
return false;
|
||||
|
@ -238,7 +227,8 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d)
|
|||
// check cache
|
||||
if (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)) {
|
||||
memcpy(output, cachedata.start, cachedata.len);
|
||||
goto done;
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "common/common.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_UNIFORM_ENTRIES 20
|
||||
#define SC_FILE_ENTRIES 10
|
||||
|
||||
enum uniform_type {
|
||||
UT_invalid,
|
||||
|
@ -484,6 +486,11 @@ struct sc_uniform {
|
|||
} v;
|
||||
};
|
||||
|
||||
struct sc_file {
|
||||
char *path;
|
||||
char *body;
|
||||
};
|
||||
|
||||
struct sc_entry {
|
||||
GLuint gl_shader;
|
||||
// the following fields define the shader's contents
|
||||
|
@ -494,9 +501,11 @@ struct sc_entry {
|
|||
struct gl_shader_cache {
|
||||
GL *gl;
|
||||
struct mp_log *log;
|
||||
struct mpv_global *global;
|
||||
|
||||
// this is modified during use (gl_sc_add() etc.)
|
||||
char *text;
|
||||
char *header_text;
|
||||
struct gl_vao *vao;
|
||||
|
||||
struct sc_entry entries[SC_ENTRIES];
|
||||
|
@ -504,15 +513,21 @@ struct gl_shader_cache {
|
|||
|
||||
struct sc_uniform uniforms[SC_UNIFORM_ENTRIES];
|
||||
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);
|
||||
*sc = (struct gl_shader_cache){
|
||||
.gl = gl,
|
||||
.log = log,
|
||||
.global = global,
|
||||
.text = talloc_strdup(sc, ""),
|
||||
.header_text = talloc_strdup(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)
|
||||
{
|
||||
sc->text[0] = '\0';
|
||||
sc->header_text[0] = '\0';
|
||||
for (int n = 0; n < sc->num_uniforms; n++)
|
||||
talloc_free(sc->uniforms[n].name);
|
||||
sc->num_uniforms = 0;
|
||||
|
@ -555,6 +571,40 @@ void gl_sc_addf(struct gl_shader_cache *sc, const char *textf, ...)
|
|||
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,
|
||||
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;
|
||||
}
|
||||
|
||||
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])
|
||||
{
|
||||
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;
|
||||
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);
|
||||
GLuint prog = gl->CreateProgram();
|
||||
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];
|
||||
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, "%s", sc->text);
|
||||
// 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 *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_add(struct gl_shader_cache *sc, const char *text);
|
||||
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,
|
||||
int unit);
|
||||
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_vec3(struct gl_shader_cache *sc, char *name, GLfloat f[3]);
|
||||
void gl_sc_uniform_mat2(struct gl_shader_cache *sc, char *name,
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <assert.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/lfg.h>
|
||||
|
||||
#include "gl_video.h"
|
||||
|
||||
|
@ -59,6 +60,7 @@ static const char *const fixed_scale_filters[] = {
|
|||
"sharpen3",
|
||||
"sharpen5",
|
||||
"oversample",
|
||||
"custom",
|
||||
NULL
|
||||
};
|
||||
static const char *const fixed_tscale_filters[] = {
|
||||
|
@ -144,6 +146,7 @@ struct src_tex {
|
|||
struct gl_video {
|
||||
GL *gl;
|
||||
|
||||
struct mpv_global *global;
|
||||
struct mp_log *log;
|
||||
struct gl_video_opts opts;
|
||||
bool gl_debug;
|
||||
|
@ -179,11 +182,17 @@ struct gl_video {
|
|||
|
||||
struct video_image image;
|
||||
|
||||
struct fbotex indirect_fbo; // RGB target
|
||||
struct fbotex chroma_merge_fbo;
|
||||
struct fbotex source_fbo;
|
||||
struct fbotex indirect_fbo;
|
||||
struct fbotex blend_subs_fbo;
|
||||
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_now;
|
||||
bool is_interpolated;
|
||||
|
@ -202,10 +211,13 @@ struct gl_video {
|
|||
struct src_tex pass_tex[TEXUNIT_VIDEO_NUM];
|
||||
bool use_indirect;
|
||||
bool use_linear;
|
||||
bool use_full_range;
|
||||
float user_gamma;
|
||||
struct fbotex copy_fbos[4];
|
||||
|
||||
int frames_uploaded;
|
||||
int frames_rendered;
|
||||
AVLFG lfg;
|
||||
|
||||
// Cached because computing it can take relatively long
|
||||
int last_dither_matrix_size;
|
||||
|
@ -437,6 +449,10 @@ const struct m_sub_options gl_video_conf = {
|
|||
({"no", 0},
|
||||
{"yes", 1},
|
||||
{"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("cscale-down", "chroma is never downscaled"),
|
||||
|
@ -551,10 +567,16 @@ static void uninit_rendering(struct gl_video *p)
|
|||
gl->DeleteTextures(1, &p->dither_texture);
|
||||
p->dither_texture = 0;
|
||||
|
||||
fbotex_uninit(&p->indirect_fbo);
|
||||
fbotex_uninit(&p->chroma_merge_fbo);
|
||||
fbotex_uninit(&p->source_fbo);
|
||||
fbotex_uninit(&p->indirect_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++)
|
||||
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;
|
||||
p->video_eq.capabilities = eq_caps;
|
||||
|
||||
av_lfg_init(&p->lfg, 1);
|
||||
|
||||
debug_check_gl(p, "before video texture creation");
|
||||
|
||||
struct video_image *vimg = &p->image;
|
||||
|
@ -874,6 +898,39 @@ static void uninit_scaler(struct gl_video *p, struct scaler *scaler)
|
|||
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
|
||||
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);
|
||||
} else if (strcmp(name, "oversample") == 0) {
|
||||
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) {
|
||||
pass_sample_polar(p, scaler);
|
||||
} 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)
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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];
|
||||
if (p->image_desc.flags & MP_IMGFLAG_SUBSAMPLED &&
|
||||
strcmp(cscale->kernel.name, "bilinear") != 0) {
|
||||
struct src_tex luma = p->pass_tex[0];
|
||||
if (p->plane_count > 2) {
|
||||
// For simplicity and performance, we merge the chroma planes
|
||||
// into a single texture before scaling, so the scaler doesn't
|
||||
// need to run multiple times.
|
||||
GLSLF("// chroma merging\n");
|
||||
GLSL(vec4 color = vec4(texture(texture1, texcoord1).r,
|
||||
texture(texture2, texcoord2).r,
|
||||
0.0, 1.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;
|
||||
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);
|
||||
}
|
||||
// Non-trivial sampling is needed on the chroma plane
|
||||
bool nontrivial = p->image_desc.flags & MP_IMGFLAG_SUBSAMPLED &&
|
||||
strcmp(cscale->kernel.name, "bilinear") != 0;
|
||||
|
||||
bool merged = false;
|
||||
if (p->plane_count > 2 && (nontrivial || shader)) {
|
||||
// For simplicity and performance, we merge the chroma planes
|
||||
// into a single texture before scaling or shading, so the shader
|
||||
// doesn't need to run multiple times.
|
||||
GLSLF("// chroma merging\n");
|
||||
GLSL(vec4 color = vec4(texture(texture1, texcoord1).r,
|
||||
texture(texture2, texcoord2).r,
|
||||
0.0, 1.0);)
|
||||
// We also pull up here in this case to avoid the issues described
|
||||
// above.
|
||||
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");
|
||||
pass_sample(p, 1, &p->scaler[2], cscale, 1.0, p->image_w, p->image_h,
|
||||
chromafix);
|
||||
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->pass_tex[0] = luma; // Restore luma after scaling
|
||||
} 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;)
|
||||
if (p->plane_count == 2) {
|
||||
gl_transform_rect(chromafix, &p->pass_tex[1].src);
|
||||
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[1].src);
|
||||
if (p->plane_count > 2 && !merged) {
|
||||
gl_transform_rect(chromafix, &p->pass_tex[2].src);
|
||||
GLSL(vec2 chroma = vec2(texture(texture1, texcoord1).r,
|
||||
texture(texture2, texcoord2).r);)
|
||||
} else {
|
||||
GLSL(vec2 chroma = texture(texture1, texcoord1).rg;)
|
||||
}
|
||||
}
|
||||
|
||||
GLSL(color = vec4(texture(texture0, texcoord0).r, chroma, 1.0);)
|
||||
if (p->has_alpha && p->plane_count >= 4)
|
||||
p->pass_tex[0] = luma; // Restore the luma plane
|
||||
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;)
|
||||
if (p->use_full_range)
|
||||
GLSLF("color.a *= %f;\n", cmul);
|
||||
}
|
||||
}
|
||||
|
||||
// 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));)
|
||||
}
|
||||
|
||||
// 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
|
||||
if (!p->is_rgb) {
|
||||
struct mp_cmat m = {{{0}}};
|
||||
|
@ -1870,12 +2003,18 @@ static void pass_render_frame(struct gl_video *p)
|
|||
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);
|
||||
|
||||
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) {
|
||||
// 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 = {
|
||||
.w = vp_w, .h = vp_h,
|
||||
.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)
|
||||
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)
|
||||
|
@ -2170,6 +2311,7 @@ static void gl_video_upload_image(struct gl_video *p)
|
|||
return;
|
||||
|
||||
vimg->needs_upload = false;
|
||||
p->frames_uploaded++;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
*p = (struct gl_video) {
|
||||
.gl = gl,
|
||||
.global = g,
|
||||
.log = log,
|
||||
.opts = gl_video_opts_def,
|
||||
.gl_target = GL_TEXTURE_2D,
|
||||
.texture_16bit_depth = 16,
|
||||
.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);
|
||||
init_gl(p);
|
||||
|
|
|
@ -66,6 +66,10 @@ struct gl_video_opts {
|
|||
struct m_color background;
|
||||
int interpolation;
|
||||
int blend_subs;
|
||||
char *source_shader;
|
||||
char *scale_shader;
|
||||
char **pre_shaders;
|
||||
char **post_shaders;
|
||||
};
|
||||
|
||||
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 *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_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,
|
||||
|
|
|
@ -434,7 +434,7 @@ static int preinit(struct vo *vo)
|
|||
}
|
||||
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)
|
||||
goto err_out;
|
||||
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,
|
||||
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)
|
||||
return MPV_ERROR_UNSUPPORTED;
|
||||
|
||||
|
|
Loading…
Reference in New Issue