summaryrefslogtreecommitdiffstats
path: root/video/out/gl_video.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/gl_video.c')
-rw-r--r--video/out/gl_video.c211
1 files changed, 177 insertions, 34 deletions
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 <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);