summaryrefslogtreecommitdiffstats
path: root/video/out
diff options
context:
space:
mode:
authorMartin Herkt <lachs0r@srsfckn.biz>2016-11-20 18:15:08 +0100
committerMartin Herkt <lachs0r@srsfckn.biz>2016-11-20 18:15:08 +0100
commit8700700de8a4103724796077034f7f254ad974bc (patch)
tree2fce4dee518a202c45c3f16567db36edc92ed914 /video/out
parente6b85c91700bee0ddc92e98a30d5021691bd6f65 (diff)
parenteafc273d2c2ae6d247d741202e58ca23dc938cb2 (diff)
downloadmpv-8700700de8a4103724796077034f7f254ad974bc.tar.bz2
mpv-8700700de8a4103724796077034f7f254ad974bc.tar.xz
Merge branch 'master' into release/current
Diffstat (limited to 'video/out')
-rw-r--r--video/out/cocoa_common.m11
-rw-r--r--video/out/filter_kernels.c49
-rw-r--r--video/out/filter_kernels.h1
-rw-r--r--video/out/opengl/common.h3
-rw-r--r--video/out/opengl/context.c2
-rw-r--r--video/out/opengl/context_rpi.c2
-rw-r--r--video/out/opengl/header_fixes.h17
-rw-r--r--video/out/opengl/video.c103
-rw-r--r--video/out/opengl/video.h1
-rw-r--r--video/out/vo.c5
-rw-r--r--video/out/vo.h1
-rw-r--r--video/out/vo_direct3d.c2
-rw-r--r--video/out/vo_opengl_cb.c11
-rw-r--r--video/out/vo_tct.c306
-rw-r--r--video/out/win_state.c2
15 files changed, 453 insertions, 63 deletions
diff --git a/video/out/cocoa_common.m b/video/out/cocoa_common.m
index ba241f42cc..719169df48 100644
--- a/video/out/cocoa_common.m
+++ b/video/out/cocoa_common.m
@@ -121,9 +121,14 @@ static void run_on_main_thread(struct vo *vo, void(^block)(void))
static void queue_new_video_size(struct vo *vo, int w, int h)
{
struct vo_cocoa_state *s = vo->cocoa;
+ struct mp_vo_opts *opts = vo->opts;
if ([s->window conformsToProtocol: @protocol(MpvSizing)]) {
id<MpvSizing> win = (id<MpvSizing>) s->window;
- [win queueNewVideoSize:NSMakeSize(w, h)];
+ NSRect r = NSMakeRect(0, 0, w, h);
+ if(!opts->hidpi_window_scale) {
+ r = [s->current_screen convertRectFromBacking:r];
+ }
+ [win queueNewVideoSize:NSMakeSize(r.size.width, r.size.height)];
}
}
@@ -488,8 +493,10 @@ static void create_ui(struct vo *vo, struct mp_rect *win, int geo_flags)
if (s->embedded) {
parent = (NSView *) (intptr_t) opts->WinID;
} else {
- const NSRect wr =
+ NSRect wr =
NSMakeRect(win->x0, win->y0, win->x1 - win->x0, win->y1 - win->y0);
+ if(!opts->hidpi_window_scale)
+ wr = [s->current_screen convertRectFromBacking:wr];
s->window = create_window(wr, s->current_screen, opts->border, adapter);
parent = [s->window contentView];
}
diff --git a/video/out/filter_kernels.c b/video/out/filter_kernels.c
index fe5265c70c..c5a12295f7 100644
--- a/video/out/filter_kernels.c
+++ b/video/out/filter_kernels.c
@@ -92,34 +92,45 @@ bool mp_init_filter(struct filter_kernel *filter, const int *sizes,
}
}
-// Sample from the blurred, windowed kernel. Note: The window is always
-// stretched to the true radius, regardless of the filter blur/scale.
-static double sample_filter(struct filter_kernel *filter,
- struct filter_window *window, double x)
+// Sample from a blurred and tapered window
+static double sample_window(struct filter_window *kernel, double x)
{
- double bk = filter->f.blur > 0.0 ? filter->f.blur : 1.0;
- double bw = window->blur > 0.0 ? window->blur : 1.0;
- double c = fabs(x) / (filter->inv_scale * bk);
- double w = window->weight ? window->weight(window, x/bw * window->radius
- / filter->f.radius)
- : 1.0;
- double v = c < filter->f.radius ? w * filter->f.weight(&filter->f, c) : 0.0;
- return filter->clamp ? fmax(0.0, fmin(1.0, v)) : v;
+ if (!kernel->weight)
+ return 1.0;
+
+ // All windows are symmetric, this makes life easier
+ x = fabs(x);
+ if (x >= kernel->radius)
+ return 0.0;
+
+ // Stretch and taper the window size as needed
+ x = kernel->blur > 0.0 ? x / kernel->blur : x;
+ x = x <= kernel->taper ? 0.0 : (x - kernel->taper) / (1 - kernel->taper);
+
+ return kernel->weight(kernel, x);
+}
+
+// Evaluate a filter's kernel and window at a given absolute position
+static double sample_filter(struct filter_kernel *filter, double x)
+{
+ // The window is always stretched to the entire kernel
+ double w = sample_window(&filter->w, x / filter->f.radius * filter->w.radius);
+ double k = sample_window(&filter->f, x / filter->inv_scale);
+ return filter->clamp ? fmax(0.0, fmin(1.0, w * k)) : w * k;
}
// Calculate the 1D filtering kernel for N sample points.
// N = number of samples, which is filter->size
// The weights will be stored in out_w[0] to out_w[N - 1]
// f = x0 - abs(x0), subpixel position in the range [0,1) or [0,1].
-static void mp_compute_weights(struct filter_kernel *filter,
- struct filter_window *window,
- double f, float *out_w)
+static void mp_compute_weights(struct filter_kernel *filter, double f,
+ float *out_w)
{
assert(filter->size > 0);
double sum = 0;
for (int n = 0; n < filter->size; n++) {
double x = f - (n - filter->size / 2 + 1);
- double w = sample_filter(filter, window, x);
+ double w = sample_filter(filter, x);
out_w[n] = w;
sum += w;
}
@@ -138,17 +149,16 @@ static void mp_compute_weights(struct filter_kernel *filter,
// [0.5 / count, 1.0 - 0.5 / count].
void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array)
{
- struct filter_window *window = &filter->w;
if (filter->polar) {
// Compute a 1D array indexed by radius
for (int x = 0; x < count; x++) {
double r = x * filter->f.radius / (count - 1);
- out_array[x] = sample_filter(filter, window, r);
+ out_array[x] = sample_filter(filter, r);
}
} else {
// Compute a 2D array indexed by subpixel position
for (int n = 0; n < count; n++) {
- mp_compute_weights(filter, window, n / (double)(count - 1),
+ mp_compute_weights(filter, n / (double)(count - 1),
out_array + filter->size * n);
}
}
@@ -321,6 +331,7 @@ const struct filter_window mp_filter_windows[] = {
{"triangle", 1, triangle},
{"bartlett", 1, triangle},
{"hanning", 1, hanning},
+ {"tukey", 1, hanning, .taper = 0.5},
{"hamming", 1, hamming},
{"quadric", 1.5, quadric},
{"welch", 1, welch},
diff --git a/video/out/filter_kernels.h b/video/out/filter_kernels.h
index 2354ef4d0c..fc90a1cdde 100644
--- a/video/out/filter_kernels.h
+++ b/video/out/filter_kernels.h
@@ -22,6 +22,7 @@ struct filter_window {
double params[2]; // User-defined custom filter parameters. Not used by
// all filters
double blur; // Blur coefficient (sharpens or widens the filter)
+ double taper; // Taper coefficient (flattens the filter's center)
};
struct filter_kernel {
diff --git a/video/out/opengl/common.h b/video/out/opengl/common.h
index 5abe839b8d..afb5b61f7e 100644
--- a/video/out/opengl/common.h
+++ b/video/out/opengl/common.h
@@ -36,6 +36,9 @@
#include <OpenGL/gl.h>
#include <OpenGL/gl3.h>
#include <OpenGL/glext.h>
+#elif HAVE_IOS_GL
+#include <OpenGLES/ES2/glext.h>
+#include <OpenGLES/ES3/glext.h>
#elif HAVE_ANDROID_GL
#include <GLES3/gl3.h>
#else
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
index 0f5b61e37c..fb3471cd3b 100644
--- a/video/out/opengl/context.c
+++ b/video/out/opengl/context.c
@@ -125,7 +125,7 @@ int mpgl_validate_backend_opt(struct mp_log *log, const struct m_option *opt,
#if HAVE_C11_TLS
#define MP_TLS _Thread_local
-#elif defined(__GNUC__)
+#elif HAVE_GCC_TLS
#define MP_TLS __thread
#endif
diff --git a/video/out/opengl/context_rpi.c b/video/out/opengl/context_rpi.c
index 96c8199ef4..fa19a6c205 100644
--- a/video/out/opengl/context_rpi.c
+++ b/video/out/opengl/context_rpi.c
@@ -42,7 +42,7 @@ struct priv {
EGL_DISPMANX_WINDOW_T egl_window;
int x, y, w, h;
double display_fps;
- atomic_bool reload_display;
+ atomic_int reload_display;
int win_params[4];
};
diff --git a/video/out/opengl/header_fixes.h b/video/out/opengl/header_fixes.h
index 9953f7e497..9a7108dcda 100644
--- a/video/out/opengl/header_fixes.h
+++ b/video/out/opengl/header_fixes.h
@@ -92,6 +92,23 @@
#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB
#endif
+#if HAVE_IOS_GL
+#define GL_WRITE_ONLY GL_WRITE_ONLY_OES
+#define GL_TEXTURE_1D 0x0DE0
+#define GL_R16 0x822A
+#define GL_RG16 0x822C
+#define GL_RGB10 0x8052
+#define GL_RGB16 0x8054
+#define GL_RGBA12 0x805A
+#define GL_RGBA16 0x805B
+#define GL_LUMINANCE8 GL_LUMINANCE8_EXT
+#define GL_LUMINANCE8_ALPHA8 GL_LUMINANCE8_ALPHA8_EXT
+#define GL_LUMINANCE16 0x8042
+#define GL_LUMINANCE16_ALPHA16 0x8048
+#define GL_TEXTURE_RED_SIZE 0x805C
+#define GL_TEXTURE_LUMINANCE_SIZE 0x8060
+#endif
+
// GL_ARB_timer_query and EXT_disjoint_timer_query
#ifndef GL_TIME_ELAPSED
// Same as GL_TIME_ELAPSED_EXT
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c
index 9461153615..498d89259e 100644
--- a/video/out/opengl/video.c
+++ b/video/out/opengl/video.c
@@ -104,6 +104,7 @@ struct texplane {
struct video_image {
struct texplane planes[4];
struct mp_image *mpi; // original input image
+ uint64_t id; // unique ID identifying mpi contents
bool hwdec_mapped;
};
@@ -153,6 +154,7 @@ struct tex_hook {
struct fbosurface {
struct fbotex fbotex;
+ uint64_t id;
double pts;
};
@@ -322,6 +324,7 @@ static const struct gl_video_opts gl_video_opts_def = {
.target_brightness = 250,
.hdr_tone_mapping = TONE_MAPPING_HABLE,
.tone_mapping_param = NAN,
+ .early_flush = -1,
};
static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt,
@@ -337,7 +340,10 @@ static int validate_window_opt(struct mp_log *log, const m_option_t *opt,
OPT_FLOAT(n"-param1", scaler[i].kernel.params[0], 0), \
OPT_FLOAT(n"-param2", scaler[i].kernel.params[1], 0), \
OPT_FLOAT(n"-blur", scaler[i].kernel.blur, 0), \
+ OPT_FLOATRANGE(n"-taper", scaler[i].kernel.taper, 0, 0.0, 1.0), \
OPT_FLOAT(n"-wparam", scaler[i].window.params[0], 0), \
+ OPT_FLOAT(n"-wblur", scaler[i].window.blur, 0), \
+ OPT_FLOATRANGE(n"-wtaper", scaler[i].window.taper, 0, 0.0, 1.0), \
OPT_FLAG(n"-clamp", scaler[i].clamp, 0), \
OPT_FLOATRANGE(n"-radius", scaler[i].radius, 0, 0.5, 16.0), \
OPT_FLOATRANGE(n"-antiring", scaler[i].antiring, 0, 0.0, 1.0), \
@@ -412,7 +418,8 @@ const struct m_sub_options gl_video_conf = {
OPT_INTRANGE("opengl-tex-pad-x", tex_pad_x, 0, 0, 4096),
OPT_INTRANGE("opengl-tex-pad-y", tex_pad_y, 0, 0, 4096),
OPT_SUBSTRUCT("", icc_opts, mp_icc_conf, 0),
- OPT_FLAG("opengl-early-flush", early_flush, 0),
+ OPT_CHOICE("opengl-early-flush", early_flush, 0,
+ ({"no", 0}, {"yes", 1}, {"auto", -1})),
{0}
},
@@ -515,7 +522,8 @@ static void uninit_scaler(struct gl_video *p, struct scaler *scaler);
static void check_gl_features(struct gl_video *p);
static bool init_format(struct gl_video *p, int fmt, bool test_only);
static void init_image_desc(struct gl_video *p, int fmt);
-static bool gl_video_upload_image(struct gl_video *p, struct mp_image *mpi);
+static bool gl_video_upload_image(struct gl_video *p, struct mp_image *mpi,
+ uint64_t id);
static const char *handle_scaler_opt(const char *name, bool tscale);
static void reinit_from_options(struct gl_video *p);
static void get_scale_factors(struct gl_video *p, bool transpose_rot, double xy[2]);
@@ -563,8 +571,10 @@ void gl_video_set_debug(struct gl_video *p, bool enable)
static void gl_video_reset_surfaces(struct gl_video *p)
{
- for (int i = 0; i < FBOSURFACES_MAX; i++)
+ for (int i = 0; i < FBOSURFACES_MAX; i++) {
+ p->surfaces[i].id = 0;
p->surfaces[i].pts = MP_NOPTS_VALUE;
+ }
p->surface_idx = 0;
p->surface_now = 0;
p->frames_drawn = 0;
@@ -951,6 +961,7 @@ static void unmap_current_image(struct gl_video *p)
p->hwdec->driver->unmap(p->hwdec);
memset(vimg->planes, 0, sizeof(vimg->planes));
vimg->hwdec_mapped = false;
+ vimg->id = 0; // needs to be mapped again
}
}
@@ -958,6 +969,7 @@ static void unref_current_image(struct gl_video *p)
{
unmap_current_image(p);
mp_image_unrefp(&p->image.mpi);
+ p->image.id = 0;
}
static void uninit_video(struct gl_video *p)
@@ -1349,7 +1361,8 @@ static bool scaler_fun_eq(struct scaler_fun a, struct scaler_fun b)
return ((!a.name && !b.name) || strcmp(a.name, b.name) == 0) &&
double_seq(a.params[0], b.params[0]) &&
double_seq(a.params[1], b.params[1]) &&
- a.blur == b.blur;
+ a.blur == b.blur &&
+ a.taper == b.taper;
}
static bool scaler_conf_eq(struct scaler_config a, struct scaler_config b)
@@ -1410,6 +1423,11 @@ static void reinit_scaler(struct gl_video *p, struct scaler *scaler,
if (conf->window.blur > 0.0)
scaler->kernel->w.blur = conf->window.blur;
+ if (conf->kernel.taper > 0.0)
+ scaler->kernel->f.taper = conf->kernel.taper;
+ if (conf->window.taper > 0.0)
+ scaler->kernel->w.taper = conf->window.taper;
+
if (scaler->kernel->f.resizable && conf->radius > 0.0)
scaler->kernel->f.radius = conf->radius;
@@ -1995,8 +2013,6 @@ static void pass_convert_yuv(struct gl_video *p)
p->components = 3;
if (!p->has_alpha || p->opts.alpha_mode == ALPHA_NO) {
GLSL(color.a = 1.0;)
- } else if (p->opts.alpha_mode == ALPHA_BLEND) {
- GLSL(color = vec4(color.rgb * color.a, 1.0);)
} else { // alpha present in image
p->components = 4;
GLSL(color = vec4(color.rgb * color.a, color.a);)
@@ -2519,12 +2535,20 @@ static void pass_draw_to_screen(struct gl_video *p, int fbo)
pass_colormanage(p, p->image_params.color, false);
- // Draw checkerboard pattern to indicate transparency
- if (p->has_alpha && p->opts.alpha_mode == ALPHA_BLEND_TILES) {
- GLSLF("// transparency checkerboard\n");
- GLSL(bvec2 tile = lessThan(fract(gl_FragCoord.xy / 32.0), vec2(0.5));)
- GLSL(vec3 background = vec3(tile.x == tile.y ? 1.0 : 0.75);)
- GLSL(color.rgb = mix(background, color.rgb, color.a);)
+ if (p->has_alpha){
+ if (p->opts.alpha_mode == ALPHA_BLEND_TILES) {
+ // Draw checkerboard pattern to indicate transparency
+ GLSLF("// transparency checkerboard\n");
+ GLSL(bvec2 tile = lessThan(fract(gl_FragCoord.xy / 32.0), vec2(0.5));)
+ GLSL(vec3 background = vec3(tile.x == tile.y ? 1.0 : 0.75);)
+ GLSL(color.rgb = mix(background, color.rgb, color.a);)
+ } else if (p->opts.alpha_mode == ALPHA_BLEND) {
+ // Blend into background color (usually black)
+ struct m_color c = p->opts.background;
+ GLSLF("vec4 background = vec4(%f, %f, %f, %f);\n",
+ c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0);
+ GLSL(color = mix(background, vec4(color.rgb, 1.0), color.a);)
+ }
}
pass_opt_hook_point(p, "OUTPUT", NULL);
@@ -2550,22 +2574,23 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
// First of all, figure out if we have a frame available at all, and draw
// it manually + reset the queue if not
- if (p->surfaces[p->surface_now].pts == MP_NOPTS_VALUE) {
- if (!gl_video_upload_image(p, t->current))
+ if (p->surfaces[p->surface_now].id == 0) {
+ if (!gl_video_upload_image(p, t->current, t->frame_id))
return;
pass_render_frame(p);
finish_pass_fbo(p, &p->surfaces[p->surface_now].fbotex,
vp_w, vp_h, FBOTEX_FUZZY);
+ p->surfaces[p->surface_now].id = p->image.id;
p->surfaces[p->surface_now].pts = p->image.mpi->pts;
p->surface_idx = p->surface_now;
}
// Find the right frame for this instant
- if (t->current && t->current->pts != MP_NOPTS_VALUE) {
+ if (t->current) {
int next = fbosurface_wrap(p->surface_now + 1);
- while (p->surfaces[next].pts != MP_NOPTS_VALUE &&
- p->surfaces[next].pts > p->surfaces[p->surface_now].pts &&
- p->surfaces[p->surface_now].pts < t->current->pts)
+ while (p->surfaces[next].id &&
+ p->surfaces[next].id > p->surfaces[p->surface_now].id &&
+ p->surfaces[p->surface_now].id < t->frame_id)
{
p->surface_now = next;
next = fbosurface_wrap(next + 1);
@@ -2607,16 +2632,17 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
break;
struct mp_image *f = t->frames[i];
- if (!mp_image_params_equal(&f->params, &p->real_image_params) ||
- f->pts == MP_NOPTS_VALUE)
+ uint64_t f_id = t->frame_id + i;
+ if (!mp_image_params_equal(&f->params, &p->real_image_params))
continue;
- if (f->pts > p->surfaces[p->surface_idx].pts) {
- if (!gl_video_upload_image(p, f))
+ if (f_id > p->surfaces[p->surface_idx].id) {
+ if (!gl_video_upload_image(p, f, f_id))
return;
pass_render_frame(p);
finish_pass_fbo(p, &p->surfaces[surface_dst].fbotex,
vp_w, vp_h, FBOTEX_FUZZY);
+ p->surfaces[surface_dst].id = f_id;
p->surfaces[surface_dst].pts = f->pts;
p->surface_idx = surface_dst;
surface_dst = fbosurface_wrap(surface_dst + 1);
@@ -2631,11 +2657,9 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
bool valid = true;
for (int i = surface_bse, ii; valid && i != surface_end; i = ii) {
ii = fbosurface_wrap(i + 1);
- if (p->surfaces[i].pts == MP_NOPTS_VALUE ||
- p->surfaces[ii].pts == MP_NOPTS_VALUE)
- {
+ if (p->surfaces[i].id == 0 || p->surfaces[ii].id == 0) {
valid = false;
- } else if (p->surfaces[ii].pts < p->surfaces[i].pts) {
+ } else if (p->surfaces[ii].id < p->surfaces[i].id) {
valid = false;
MP_DBG(p, "interpolation queue underrun\n");
}
@@ -2656,8 +2680,8 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
// (which requires some extra checking to make sure it's valid)
if (mix < 0.0) {
int prev = fbosurface_wrap(surface_bse - 1);
- if (p->surfaces[prev].pts != MP_NOPTS_VALUE &&
- p->surfaces[prev].pts < p->surfaces[surface_bse].pts)
+ if (p->surfaces[prev].id != 0 &&
+ p->surfaces[prev].id < p->surfaces[surface_bse].id)
{
mix += 1.0;
surface_bse = prev;
@@ -2736,7 +2760,6 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo)
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
bool has_frame = !!frame->current;
- bool is_new = has_frame && !frame->redraw && !frame->repeat;
if (!has_frame || p->dst_rect.x0 > 0 || p->dst_rect.y0 > 0 ||
p->dst_rect.x1 < p->vp_w || p->dst_rect.y1 < abs(p->vp_h))
@@ -2758,7 +2781,7 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo)
gl->Disable(GL_SCISSOR_TEST);
}
- if (is_new || !frame->current)
+ if (frame->frame_id != p->image.id || !frame->current)
p->hwdec->driver->overlay_frame(p->hwdec, frame->current);
if (frame->current)
@@ -2782,10 +2805,16 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo)
if (interpolate) {
gl_video_interpolate_frame(p, frame, fbo);
} else {
+ bool is_new = frame->frame_id != p->image.id;
+
+ // Redrawing a frame might update subtitles.
+ if (frame->still && p->opts.blend_subs)
+ is_new = true;
+
if (is_new || !p->output_fbo_valid) {
p->output_fbo_valid = false;
- if (!gl_video_upload_image(p, frame->current))
+ if (!gl_video_upload_image(p, frame->current, frame->frame_id))
goto done;
pass_render_frame(p);
@@ -2849,8 +2878,11 @@ done:
// The playloop calls this last before waiting some time until it decides
// to call flip_page(). Tell OpenGL to start execution of the GPU commands
// while we sleep (this happens asynchronously).
- if (p->opts.early_flush)
+ if ((p->opts.early_flush == -1 && !frame->display_synced) ||
+ p->opts.early_flush == 1)
+ {
gl->Flush();
+ }
p->frames_rendered++;
@@ -2944,11 +2976,15 @@ static void reinterleave_vdpau(struct gl_video *p, struct gl_hwdec_frame *frame)
}
// Returns false on failure.
-static bool gl_video_upload_image(struct gl_video *p, struct mp_image *mpi)
+static bool gl_video_upload_image(struct gl_video *p, struct mp_image *mpi,
+ uint64_t id)
{
GL *gl = p->gl;
struct video_image *vimg = &p->image;
+ if (vimg->id == id)
+ return true;
+
unref_current_image(p);
mpi = mp_image_new_ref(mpi);
@@ -2956,6 +2992,7 @@ static bool gl_video_upload_image(struct gl_video *p, struct mp_image *mpi)
goto error;
vimg->mpi = mpi;
+ vimg->id = id;
p->osd_pts = mpi->pts;
p->frames_uploaded++;
diff --git a/video/out/opengl/video.h b/video/out/opengl/video.h
index 5011af83d1..54b7022f27 100644
--- a/video/out/opengl/video.h
+++ b/video/out/opengl/video.h
@@ -35,6 +35,7 @@ struct scaler_fun {
char *name;
float params[2];
float blur;
+ float taper;
};
struct scaler_config {
diff --git a/video/out/vo.c b/video/out/vo.c
index 4b28aadaa1..cbd2ca87c8 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -59,6 +59,7 @@ extern const struct vo_driver video_out_sdl;
extern const struct vo_driver video_out_vaapi;
extern const struct vo_driver video_out_wayland;
extern const struct vo_driver video_out_rpi;
+extern const struct vo_driver video_out_tct;
const struct vo_driver *const video_out_drivers[] =
{
@@ -89,6 +90,7 @@ const struct vo_driver *const video_out_drivers[] =
&video_out_null,
// should not be auto-selected
&video_out_image,
+ &video_out_tct,
#if HAVE_CACA
&video_out_caca,
#endif
@@ -399,7 +401,7 @@ static void vsync_skip_detection(struct vo *vo)
}
int64_t desync = diff / in->num_vsync_samples;
if (in->drop_point > window * 2 &&
- labs(desync - desync_early) >= in->vsync_interval * 3 / 4)
+ llabs(desync - desync_early) >= in->vsync_interval * 3 / 4)
{
// Assume a drop. An underflow can technically speaking not be a drop
// (it's up to the driver what this is supposed to mean), but no reason
@@ -871,6 +873,7 @@ static void do_redraw(struct vo *vo)
if (!frame)
frame = &dummy;
frame->redraw = !full_redraw; // unconditionally redraw if it was dropped
+ frame->repeat = false;
frame->still = true;
frame->pts = 0;
frame->duration = -1;
diff --git a/video/out/vo.h b/video/out/vo.h
index d2393f829b..99e6ccabae 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -223,6 +223,7 @@ struct vo_frame {
// a frame is guaranteed not to change (instant redraws will use the same
// ID). frames[n] has the ID frame_id+n, with the guarantee that frame
// drops or reconfigs will keep the guarantee.
+ // The ID is never 0 (unless num_frames==0). IDs are strictly monotonous.
uint64_t frame_id;
};
diff --git a/video/out/vo_direct3d.c b/video/out/vo_direct3d.c
index 38dde55af2..c99ea372f6 100644
--- a/video/out/vo_direct3d.c
+++ b/video/out/vo_direct3d.c
@@ -1466,13 +1466,13 @@ static mp_image_t *get_window_screenshot(d3d_priv *priv)
POINT pt;
D3DLOCKED_RECT locked_rect;
int width, height;
+ IDirect3DSurface9 *surface = NULL;
if (FAILED(IDirect3DDevice9_GetDisplayMode(priv->d3d_device, 0, &mode))) {
MP_ERR(priv, "GetDisplayMode failed.\n");
goto error_exit;
}
- IDirect3DSurface9 *surface = NULL;
if (FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(priv->d3d_device,
mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface,
NULL)))
diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c
index 20e9523cb0..c66f6d434c 100644
--- a/video/out/vo_opengl_cb.c
+++ b/video/out/vo_opengl_cb.c
@@ -296,7 +296,7 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
int64_t wait_present_count = ctx->present_count;
if (frame) {
ctx->next_frame = NULL;
- if (frame->redraw || !frame->current)
+ if (!(frame->redraw || !frame->current))
wait_present_count += 1;
pthread_cond_signal(&ctx->wakeup);
talloc_free(ctx->cur_frame);
@@ -371,8 +371,10 @@ static void flip_page(struct vo *vo)
// Wait until frame was rendered
while (p->ctx->next_frame) {
if (pthread_cond_timedwait(&p->ctx->wakeup, &p->ctx->lock, &ts)) {
- MP_VERBOSE(vo, "mpv_opengl_cb_draw() not being called or stuck.\n");
- goto done;
+ if (p->ctx->next_frame) {
+ MP_VERBOSE(vo, "mpv_opengl_cb_draw() not being called or stuck.\n");
+ goto done;
+ }
}
}
@@ -399,7 +401,8 @@ done:
// Cleanup after the API user is not reacting, or is being unusually slow.
if (p->ctx->next_frame) {
- talloc_free(p->ctx->next_frame);
+ talloc_free(p->ctx->cur_frame);
+ p->ctx->cur_frame = p->ctx->next_frame;
p->ctx->next_frame = NULL;
p->ctx->present_count += 2;
pthread_cond_signal(&p->ctx->wakeup);
diff --git a/video/out/vo_tct.c b/video/out/vo_tct.c
new file mode 100644
index 0000000000..68b29e9bc8
--- /dev/null
+++ b/video/out/vo_tct.c
@@ -0,0 +1,306 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <config.h>
+
+#if HAVE_POSIX
+#include <sys/ioctl.h>
+#endif
+
+#include <libswscale/swscale.h>
+
+#include "options/m_config.h"
+#include "config.h"
+#include "vo.h"
+#include "sub/osd.h"
+#include "video/sws_utils.h"
+#include "video/mp_image.h"
+
+#define IMGFMT IMGFMT_BGR24
+
+#define ALGO_PLAIN 1
+#define ALGO_HALF_BLOCKS 2
+#define ESC_HIDE_CURSOR "\e[?25l"
+#define ESC_RESTORE_CURSOR "\e[?25h"
+#define ESC_CLEAR_SCREEN "\e[2J"
+#define ESC_CLEAR_COLORS "\e[0m"
+#define ESC_GOTOXY "\e[%d;%df"
+#define ESC_COLOR_BG "\e[48;2;%d;%d;%dm"
+#define ESC_COLOR_FG "\e[38;2;%d;%d;%dm"
+#define ESC_COLOR256_BG "\e[48;5;%dm"
+#define ESC_COLOR256_FG "\e[38;5;%dm"
+#define DEFAULT_WIDTH 80
+#define DEFAULT_HEIGHT 25
+
+struct vo_tct_opts {
+ int algo;
+ int width; // 0 -> default
+ int height; // 0 -> default
+ int term256; // 0 -> true color
+};
+
+#define OPT_BASE_STRUCT struct vo_tct_opts
+static const struct m_sub_options vo_tct_conf = {
+ .opts = (const m_option_t[]) {
+ OPT_CHOICE("vo-tct-algo", algo, 0,
+ ({"plain", ALGO_PLAIN},
+ {"half-blocks", ALGO_HALF_BLOCKS})),
+ OPT_INT("vo-tct-width", width, 0),
+ OPT_INT("vo-tct-height", height, 0),
+ OPT_FLAG("vo-tct-256", term256, 0),
+ {0}
+ },
+ .defaults = &(const struct vo_tct_opts) {
+ .algo = ALGO_HALF_BLOCKS,
+ },
+ .size = sizeof(struct vo_tct_opts),
+};
+
+struct priv {
+ struct vo_tct_opts *opts;
+ size_t buffer_size;
+ char *buffer;
+ int swidth;
+ int sheight;
+ struct mp_image *frame;
+ struct mp_rect src;
+ struct mp_rect dst;
+ struct mp_sws_context *sws;
+};
+
+// Convert RGB24 to xterm-256 8-bit value
+// For simplicity, assume RGB space is perceptually uniform.
+// There are 5 places where one of two outputs needs to be chosen when the
+// input is the exact middle:
+// - The r/g/b channels and the gray value: the higher value output is chosen.
+// - If the gray and color have same distance from the input - color is chosen.
+static int rgb_to_x256(uint8_t r, uint8_t g, uint8_t b)
+{
+ // Calculate the nearest 0-based color index at 16 .. 231
+# define v2ci(v) (v < 48 ? 0 : v < 115 ? 1 : (v - 35) / 40)
+ int ir = v2ci(r), ig = v2ci(g), ib = v2ci(b); // 0..5 each
+# define color_index() (36 * ir + 6 * ig + ib) /* 0..215, lazy evaluation */
+
+ // Calculate the nearest 0-based gray index at 232 .. 255
+ int average = (r + g + b) / 3;
+ int gray_index = average > 238 ? 23 : (average - 3) / 10; // 0..23
+
+ // Calculate the represented colors back from the index
+ static const int i2cv[6] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
+ int cr = i2cv[ir], cg = i2cv[ig], cb = i2cv[ib]; // r/g/b, 0..255 each
+ int gv = 8 + 10 * gray_index; // same value for r/g/b, 0..255
+
+ // Return the one which is nearer to the original input rgb value
+# define dist_square(A,B,C, a,b,c) ((A-a)*(A-a) + (B-b)*(B-b) + (C-c)*(C-c))
+ int color_err = dist_square(cr, cg, cb, r, g, b);
+ int gray_err = dist_square(gv, gv, gv, r, g, b);
+ return color_err <= gray_err ? 16 + color_index() : 232 + gray_index;
+}
+
+static void write_plain(
+ const int dwidth, const int dheight,
+ const int swidth, const int sheight,
+ const unsigned char *source, const int source_stride,
+ bool term256)
+{
+ assert(source);
+ const int tx = (dwidth - swidth) / 2;
+ const int ty = (dheight - sheight) / 2;
+ for (int y = 0; y < sheight; y++) {
+ const unsigned char *row = source + y * source_stride;
+ printf(ESC_GOTOXY, ty + y, tx);
+ for (int x = 0; x < swidth; x++) {
+ unsigned char b = *row++;
+ unsigned char g = *row++;
+ unsigned char r = *row++;
+ if (term256) {
+ printf(ESC_COLOR256_BG, rgb_to_x256(r, g, b));
+ } else {
+ printf(ESC_COLOR_BG, r, g, b);
+ }
+ printf(" ");
+ }
+ printf(ESC_CLEAR_COLORS);
+ }
+ printf("\n");
+}
+
+static void write_half_blocks(
+ const int dwidth, const int dheight,
+ const int swidth, const int sheight,
+ unsigned char *source, int source_stride,
+ bool term256)
+{
+ assert(source);
+ const int tx = (dwidth - swidth) / 2;
+ const int ty = (dheight - sheight) / 2;
+ for (int y = 0; y < sheight * 2; y += 2) {
+ const unsigned char *row_up = source + y * source_stride;
+ const unsigned char *row_down = source + (y + 1) * source_stride;
+ printf(ESC_GOTOXY, ty + y / 2, tx);
+ for (int x = 0; x < swidth; x++) {
+ unsigned char b_up = *row_up++;
+ unsigned char g_up = *row_up++;
+ unsigned char r_up = *row_up++;
+ unsigned char b_down =