From 4d6b9550fe1e0354461716f560d71d78196d5da5 Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Fri, 27 Mar 2015 13:27:40 +0100 Subject: vo_opengl: add support for custom shaders --- DOCS/man/vo.rst | 72 +++++++++++++++- stream/stream.c | 14 ++++ stream/stream.h | 2 + video/out/gl_lcms.c | 18 +--- video/out/gl_utils.c | 72 +++++++++++++++- video/out/gl_utils.h | 6 +- video/out/gl_video.c | 211 +++++++++++++++++++++++++++++++++++++++-------- video/out/gl_video.h | 6 +- video/out/vo_opengl.c | 2 +- video/out/vo_opengl_cb.c | 2 +- 10 files changed, 350 insertions(+), 55 deletions(-) diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst index 26d31f7498..455a0562c9 100644 --- a/DOCS/man/vo.rst +++ b/DOCS/man/vo.rst @@ -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=``, ``scale-shader=``, ``pre-shaders=``, ``post-shaders=`` + 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 diff --git a/stream/stream.c b/stream/stream.c index d6521586b5..35d09ad023 100644 --- a/stream/stream.c +++ b/stream/stream.c @@ -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__ diff --git a/stream/stream.h b/stream/stream.h index cda5c7434e..981c90cf5e 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -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, diff --git a/video/out/gl_lcms.c b/video/out/gl_lcms.c index b7ee51360c..ec3013c553 100644 --- a/video/out/gl_lcms.c +++ b/video/out/gl_lcms.c @@ -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; diff --git a/video/out/gl_utils.c b/video/out/gl_utils.c index f8b46d95b0..0401a0830e 100644 --- a/video/out/gl_utils.c +++ b/video/out/gl_utils.c @@ -28,6 +28,7 @@ #include #include +#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" diff --git a/video/out/gl_utils.h b/video/out/gl_utils.h index 2c55e72c44..f03ac07b83 100644 --- a/video/out/gl_utils.h +++ b/video/out/gl_utils.h @@ -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, diff --git a/video/out/gl_video.c b/video/out/gl_video.c index 3c747ccd12..867e88f267 100644 --- a/video/out/gl_video.c +++ b/video/out/gl_video.c @@ -27,6 +27,7 @@ #include #include +#include #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); diff --git a/video/out/gl_video.h b/video/out/gl_video.h index d0896e671d..1a56d6c1fc 100644 --- a/video/out/gl_video.h +++ b/video/out/gl_video.h @@ -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, diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c index 53210487f6..bb7470eaf4 100644 --- a/video/out/vo_opengl.c +++ b/video/out/vo_opengl.c @@ -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); diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c index fc4dc950fb..b3dc5ca84f 100644 --- a/video/out/vo_opengl_cb.c +++ b/video/out/vo_opengl_cb.c @@ -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; -- cgit v1.2.3