0
0
mirror of https://github.com/mpv-player/mpv.git synced 2024-09-20 03:52:22 +02:00

vo_gpu: make screenshots use the GL renderer

Using the GL renderer for color conversion will make sure screenshots
will use the same conversion as normal video rendering. It can do this
for all types of screenshots.

The logic when to write 16 bit PNGs changes. To approximate the old
behavior, we decide by looking whether the source video format has more
than 8 bits per component. We apply this logic even for window
screenshots. Also, 16 bit PNGs now always include an unused alpha
channel. The reason is that FFmpeg has RGB48 and RGBA64 formats, but no
RGB064. RGB48 is 3 bytes and usually not supported by GPUs for
rendering, so we have to use RGBA64, which forces an alpha channel.

Will break for users who use --target-trc and similar options.

I considered creating a new gl_video context, but it could double GPU
memory use, so I didn't.

This uses FBOs instead of glGetTexImage(), because that increases the
chance it could work on GLES (e.g. ANGLE). Untested. No support for the
Vulkan and D3D11 backends yet.

Fixes #5498. Also fixes #5240, because the code for reading back is not
used with the new code path.
This commit is contained in:
wm4 2018-02-07 20:18:36 +01:00 committed by Kevin Mitchell
parent 7b1e73139f
commit 9f595f3a80
16 changed files with 219 additions and 33 deletions

View File

@ -3309,7 +3309,8 @@ Screenshot
``--screenshot-high-bit-depth=<yes|no>`` ``--screenshot-high-bit-depth=<yes|no>``
If possible, write screenshots with a bit depth similar to the source If possible, write screenshots with a bit depth similar to the source
video (default: yes). This is interesting in particular for PNG, as this video (default: yes). This is interesting in particular for PNG, as this
sometimes triggers writing 16 bit PNGs with huge file sizes. sometimes triggers writing 16 bit PNGs with huge file sizes. This will also
include an unused alpha channel in the resulting files if 16 bit is used.
``--screenshot-template=<template>`` ``--screenshot-template=<template>``
Specify the filename template used to save screenshots. The template Specify the filename template used to save screenshots. The template

View File

@ -389,16 +389,30 @@ static void add_subs(struct MPContext *mpctx, struct mp_image *image)
OSD_DRAW_SUB_ONLY, image); OSD_DRAW_SUB_ONLY, image);
} }
static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode) static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode,
bool high_depth)
{ {
struct mp_image *image = NULL; struct mp_image *image = NULL;
if (mode == MODE_SUBTITLES && osd_get_render_subs_in_filter(mpctx->osd)) if (mode == MODE_SUBTITLES && osd_get_render_subs_in_filter(mpctx->osd))
mode = 0; mode = 0;
bool need_add_subs = mode == MODE_SUBTITLES;
if (mpctx->video_out && mpctx->video_out->config_ok) { if (mpctx->video_out && mpctx->video_out->config_ok) {
vo_wait_frame(mpctx->video_out); // important for each-frame mode vo_wait_frame(mpctx->video_out); // important for each-frame mode
if (mode != MODE_FULL_WINDOW) struct voctrl_screenshot ctrl = {
.scaled = mode == MODE_FULL_WINDOW,
.subs = mode != 0,
.osd = mode == MODE_FULL_WINDOW,
.high_bit_depth = high_depth &&
mpctx->opts->screenshot_image_opts->high_bit_depth,
};
vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &ctrl);
image = ctrl.res;
if (image)
need_add_subs = false;
if (!image && mode != MODE_FULL_WINDOW)
image = vo_get_current_frame(mpctx->video_out); image = vo_get_current_frame(mpctx->video_out);
if (!image) { if (!image) {
vo_control(mpctx->video_out, VOCTRL_SCREENSHOT_WIN, &image); vo_control(mpctx->video_out, VOCTRL_SCREENSHOT_WIN, &image);
@ -412,7 +426,7 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
image = nimage; image = nimage;
} }
if (image && mode == MODE_SUBTITLES) if (image && need_add_subs)
add_subs(mpctx, image); add_subs(mpctx, image);
return image; return image;
@ -420,7 +434,7 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
struct mp_image *screenshot_get_rgb(struct MPContext *mpctx, int mode) struct mp_image *screenshot_get_rgb(struct MPContext *mpctx, int mode)
{ {
struct mp_image *mpi = screenshot_get(mpctx, mode); struct mp_image *mpi = screenshot_get(mpctx, mode, false);
if (!mpi) if (!mpi)
return NULL; return NULL;
struct mp_image *res = convert_image(mpi, IMGFMT_BGR0, mpctx->log); struct mp_image *res = convert_image(mpi, IMGFMT_BGR0, mpctx->log);
@ -440,7 +454,8 @@ void screenshot_to_file(struct MPContext *mpctx, const char *filename, int mode,
int format = image_writer_format_from_ext(ext); int format = image_writer_format_from_ext(ext);
if (format) if (format)
opts.format = format; opts.format = format;
struct mp_image *image = screenshot_get(mpctx, mode); bool high_depth = image_writer_high_depth(&opts);
struct mp_image *image = screenshot_get(mpctx, mode, high_depth);
if (!image) { if (!image) {
screenshot_msg(ctx, MSGL_ERR, "Taking screenshot failed."); screenshot_msg(ctx, MSGL_ERR, "Taking screenshot failed.");
goto end; goto end;
@ -471,10 +486,12 @@ void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
ctx->mode = mode; ctx->mode = mode;
ctx->osd = osd; ctx->osd = osd;
struct mp_image *image = screenshot_get(mpctx, mode); struct image_writer_opts *opts = mpctx->opts->screenshot_image_opts;
bool high_depth = image_writer_high_depth(opts);
struct mp_image *image = screenshot_get(mpctx, mode, high_depth);
if (image) { if (image) {
struct image_writer_opts *opts = mpctx->opts->screenshot_image_opts;
char *filename = gen_fname(ctx, image_writer_file_ext(opts)); char *filename = gen_fname(ctx, image_writer_file_ext(opts));
if (filename) if (filename)
write_screenshot(mpctx, image, filename, NULL, async); write_screenshot(mpctx, image, filename, NULL, async);

View File

@ -57,6 +57,8 @@ static const struct {
{IMGFMT_0BGR, AV_PIX_FMT_ABGR}, {IMGFMT_0BGR, AV_PIX_FMT_ABGR},
#endif #endif
{IMGFMT_RGBA64, AV_PIX_FMT_RGBA64},
{IMGFMT_VDPAU, AV_PIX_FMT_VDPAU}, {IMGFMT_VDPAU, AV_PIX_FMT_VDPAU},
#if HAVE_VIDEOTOOLBOX_HWACCEL #if HAVE_VIDEOTOOLBOX_HWACCEL
{IMGFMT_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX}, {IMGFMT_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX},

View File

@ -277,6 +277,11 @@ const char *image_writer_file_ext(const struct image_writer_opts *opts)
return m_opt_choice_str(mp_image_writer_formats, opts->format); return m_opt_choice_str(mp_image_writer_formats, opts->format);
} }
bool image_writer_high_depth(const struct image_writer_opts *opts)
{
return opts->format == AV_CODEC_ID_PNG;
}
int image_writer_format_from_ext(const char *ext) int image_writer_format_from_ext(const char *ext)
{ {
for (int n = 0; mp_image_writer_formats[n].name; n++) { for (int n = 0; mp_image_writer_formats[n].name; n++) {

View File

@ -42,6 +42,9 @@ extern const struct m_option image_writer_opts[];
// Return the file extension that will be used, e.g. "png". // Return the file extension that will be used, e.g. "png".
const char *image_writer_file_ext(const struct image_writer_opts *opts); const char *image_writer_file_ext(const struct image_writer_opts *opts);
// Return whether the selected format likely supports >8 bit per component.
bool image_writer_high_depth(const struct image_writer_opts *opts);
// Map file extension to format ID - return 0 (which is invalid) if unknown. // Map file extension to format ID - return 0 (which is invalid) if unknown.
int image_writer_format_from_ext(const char *ext); int image_writer_format_from_ext(const char *ext);

View File

@ -182,6 +182,9 @@ enum mp_imgfmt {
IMGFMT_RGB0_START = IMGFMT_0RGB, IMGFMT_RGB0_START = IMGFMT_0RGB,
IMGFMT_RGB0_END = IMGFMT_RGB0, IMGFMT_RGB0_END = IMGFMT_RGB0,
// Like IMGFMT_RGBA, but 2 bytes per component.
IMGFMT_RGBA64,
// Accessed with bit-shifts after endian-swapping the uint16_t pixel // Accessed with bit-shifts after endian-swapping the uint16_t pixel
IMGFMT_RGB565, // 5r 6g 5b (MSB to LSB) IMGFMT_RGB565, // 5r 6g 5b (MSB to LSB)

View File

@ -110,6 +110,7 @@ struct ra_tex_params {
bool blit_src; // must be usable as a blit source bool blit_src; // must be usable as a blit source
bool blit_dst; // must be usable as a blit destination bool blit_dst; // must be usable as a blit destination
bool host_mutable; // texture may be updated with tex_upload bool host_mutable; // texture may be updated with tex_upload
bool downloadable; // texture can be read with tex_download
// When used as render source texture. // When used as render source texture.
bool src_linear; // if false, use nearest sampling (whether this can bool src_linear; // if false, use nearest sampling (whether this can
// be true depends on ra_format.linear_filter) // be true depends on ra_format.linear_filter)
@ -151,6 +152,13 @@ struct ra_tex_upload_params {
ptrdiff_t stride; // The size of a horizontal line in bytes (*not* texels!) ptrdiff_t stride; // The size of a horizontal line in bytes (*not* texels!)
}; };
struct ra_tex_download_params {
struct ra_tex *tex; // Texture to download from
// Downloading directly (set by caller, data written to by callee):
void *dst; // Address of data (packed with no alignment)
ptrdiff_t stride; // The size of a horizontal line in bytes (*not* texels!)
};
// Buffer usage type. This restricts what types of operations may be performed // Buffer usage type. This restricts what types of operations may be performed
// on a buffer. // on a buffer.
enum ra_buf_type { enum ra_buf_type {
@ -379,6 +387,10 @@ struct ra_fns {
// Returns whether successful. // Returns whether successful.
bool (*tex_upload)(struct ra *ra, const struct ra_tex_upload_params *params); bool (*tex_upload)(struct ra *ra, const struct ra_tex_upload_params *params);
// Copy data from the texture to memory. ra_tex_params.downloadable must
// have been set to true on texture creation.
bool (*tex_download)(struct ra *ra, struct ra_tex_download_params *params);
// Create a buffer. This can be used as a persistently mapped buffer, // Create a buffer. This can be used as a persistently mapped buffer,
// a uniform buffer, a shader storage buffer or possibly others. // a uniform buffer, a shader storage buffer or possibly others.
// Not all usage types must be supported; may return NULL if unavailable. // Not all usage types must be supported; may return NULL if unavailable.

View File

@ -2852,7 +2852,7 @@ static bool update_surface(struct gl_video *p, struct mp_image *mpi,
// Draws an interpolate frame to fbo, based on the frame timing in t // Draws an interpolate frame to fbo, based on the frame timing in t
// flags: bit set of RENDER_FRAME_* flags // flags: bit set of RENDER_FRAME_* flags
static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
struct ra_fbo fbo, flags) struct ra_fbo fbo, int flags)
{ {
bool is_new = false; bool is_new = false;
@ -3156,6 +3156,84 @@ done:
pass_report_performance(p); pass_report_performance(p);
} }
void gl_video_screenshot(struct gl_video *p, struct vo_frame *frame,
struct voctrl_screenshot *args)
{
bool ok = false;
struct mp_image *res = NULL;
if (!p->ra->fns->tex_download)
return;
struct mp_rect old_src = p->src_rect;
struct mp_rect old_dst = p->dst_rect;
struct mp_osd_res old_osd = p->osd_rect;
if (!args->scaled) {
int w = p->real_image_params.w;
int h = p->real_image_params.h;
if (w < 1 || h < 1)
return;
struct mp_rect rc = {0, 0, w, h};
struct mp_osd_res osd = {.w = w, .h = h, .display_par = 1.0};
gl_video_resize(p, &rc, &rc, &osd);
}
gl_video_reset_surfaces(p);
struct ra_tex_params params = {
.dimensions = 2,
.downloadable = true,
.w = p->osd_rect.w,
.h = p->osd_rect.h,
.render_dst = true,
};
params.format = ra_find_unorm_format(p->ra, 1, 4);
int mpfmt = IMGFMT_RGB0;
if (args->high_bit_depth && p->ra_format.component_bits > 8) {
const struct ra_format *fmt = ra_find_unorm_format(p->ra, 2, 4);
if (fmt && fmt->renderable) {
params.format = fmt;
mpfmt = IMGFMT_RGBA64;
}
}
if (!params.format || !params.format->renderable)
goto done;
struct ra_tex *target = ra_tex_create(p->ra, &params);
if (!target)
goto done;
int flags = 0;
if (args->subs)
flags |= RENDER_FRAME_SUBS;
if (args->osd)
flags |= RENDER_FRAME_OSD;
gl_video_render_frame(p, frame, (struct ra_fbo){target}, flags);
res = mp_image_alloc(mpfmt, params.w, params.h);
if (!res)
goto done;
struct ra_tex_download_params download_params = {
.tex = target,
.dst = res->planes[0],
.stride = res->stride[0],
};
if (!p->ra->fns->tex_download(p->ra, &download_params))
goto done;
ok = true;
done:
ra_tex_free(p->ra, &target);
gl_video_resize(p, &old_src, &old_dst, &old_osd);
if (!ok)
TA_FREEP(&res);
args->res = res;
}
// Use this color instead of the global option. // Use this color instead of the global option.
void gl_video_set_clear_color(struct gl_video *p, struct m_color c) void gl_video_set_clear_color(struct gl_video *p, struct m_color c)
{ {

View File

@ -146,6 +146,7 @@ extern const struct m_sub_options gl_video_conf;
struct gl_video; struct gl_video;
struct vo_frame; struct vo_frame;
struct voctrl_screenshot;
enum { enum {
RENDER_FRAME_SUBS = 1 << 0, RENDER_FRAME_SUBS = 1 << 0,
@ -172,6 +173,9 @@ void gl_video_set_osd_pts(struct gl_video *p, double pts);
bool gl_video_check_osd_change(struct gl_video *p, struct mp_osd_res *osd, bool gl_video_check_osd_change(struct gl_video *p, struct mp_osd_res *osd,
double pts); double pts);
void gl_video_screenshot(struct gl_video *p, struct vo_frame *frame,
struct voctrl_screenshot *args);
float gl_video_scale_ambient_lux(float lmin, float lmax, float gl_video_scale_ambient_lux(float lmin, float lmax,
float rmin, float rmax, float lux); float rmin, float rmax, float lux);
void gl_video_set_ambient_lux(struct gl_video *p, int lux); void gl_video_set_ambient_lux(struct gl_video *p, int lux);

View File

@ -251,16 +251,21 @@ struct mp_image *ra_gl_ctx_screenshot(struct ra_swapchain *sw)
{ {
struct priv *p = sw->priv; struct priv *p = sw->priv;
assert(p->wrapped_fb); struct mp_image *screen = mp_image_alloc(IMGFMT_RGB24, p->wrapped_fb->params.w,
struct mp_image *screen = gl_read_fbo_contents(p->gl, p->main_fb, p->wrapped_fb->params.h);
p->wrapped_fb->params.w, if (!screen)
p->wrapped_fb->params.h); return NULL;
// OpenGL FB is also read in flipped order, so we need to flip when the int dir = p->params.flipped ? 1 : -1;
// rendering is *not* flipped, which in our case is whenever
// p->params.flipped is true. I hope that made sense assert(p->wrapped_fb);
if (screen && p->params.flipped) if (!gl_read_fbo_contents(p->gl, p->main_fb, dir, GL_RGB, GL_UNSIGNED_BYTE,
mp_image_vflip(screen); p->wrapped_fb->params.w, p->wrapped_fb->params.h,
screen->planes[0], screen->stride[0]))
{
talloc_free(screen);
return NULL;
}
return screen; return screen;
} }

View File

@ -277,6 +277,13 @@ static struct ra_tex *gl_tex_create_blank(struct ra *ra,
tex_gl->target = GL_TEXTURE_EXTERNAL_OES; tex_gl->target = GL_TEXTURE_EXTERNAL_OES;
} }
if (params->downloadable && !(params->dimensions == 2 &&
params->format->renderable))
{
gl_tex_destroy(ra, tex);
return NULL;
}
return tex; return tex;
} }
@ -329,8 +336,11 @@ static struct ra_tex *gl_tex_create(struct ra *ra,
gl_check_error(gl, ra->log, "after creating texture"); gl_check_error(gl, ra->log, "after creating texture");
// Even blitting needs an FBO in OpenGL for strange reasons // Even blitting needs an FBO in OpenGL for strange reasons.
if (tex->params.render_dst || tex->params.blit_src || tex->params.blit_dst) { // Download is handled by reading from an FBO.
if (tex->params.render_dst || tex->params.blit_src ||
tex->params.blit_dst || tex->params.downloadable)
{
if (!tex->params.format->renderable) { if (!tex->params.format->renderable) {
MP_ERR(ra, "Trying to create renderable texture with unsupported " MP_ERR(ra, "Trying to create renderable texture with unsupported "
"format.\n"); "format.\n");
@ -512,6 +522,18 @@ static bool gl_tex_upload(struct ra *ra,
return true; return true;
} }
static bool gl_tex_download(struct ra *ra, struct ra_tex_download_params *params)
{
GL *gl = ra_gl_get(ra);
struct ra_tex *tex = params->tex;
struct ra_tex_gl *tex_gl = tex->priv;
if (!tex_gl->fbo)
return false;
return gl_read_fbo_contents(gl, tex_gl->fbo, 1, tex_gl->format, tex_gl->type,
tex->params.w, tex->params.h, params->dst,
params->stride);
}
static void gl_buf_destroy(struct ra *ra, struct ra_buf *buf) static void gl_buf_destroy(struct ra *ra, struct ra_buf *buf)
{ {
if (!buf) if (!buf)
@ -1134,6 +1156,7 @@ static struct ra_fns ra_fns_gl = {
.tex_create = gl_tex_create, .tex_create = gl_tex_create,
.tex_destroy = gl_tex_destroy, .tex_destroy = gl_tex_destroy,
.tex_upload = gl_tex_upload, .tex_upload = gl_tex_upload,
.tex_download = gl_tex_download,
.buf_create = gl_buf_create, .buf_create = gl_buf_create,
.buf_destroy = gl_buf_destroy, .buf_destroy = gl_buf_destroy,
.buf_update = gl_buf_update, .buf_update = gl_buf_update,

View File

@ -105,25 +105,23 @@ void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4); gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
} }
mp_image_t *gl_read_fbo_contents(GL *gl, int fbo, int w, int h) bool gl_read_fbo_contents(GL *gl, int fbo, int dir, GLenum format, GLenum type,
int w, int h, uint8_t *dst, int dst_stride)
{ {
if (gl->es) assert(dir == 1 || dir == -1);
return NULL; // ES can't read from front buffer if (fbo == 0 && gl->es)
mp_image_t *image = mp_image_alloc(IMGFMT_RGB24, w, h); return false; // ES can't read from front buffer
if (!image)
return NULL;
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo); gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
GLenum obj = fbo ? GL_COLOR_ATTACHMENT0 : GL_FRONT; GLenum obj = fbo ? GL_COLOR_ATTACHMENT0 : GL_FRONT;
gl->PixelStorei(GL_PACK_ALIGNMENT, 1); gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
gl->ReadBuffer(obj); gl->ReadBuffer(obj);
//flip image while reading (and also avoid stride-related trouble) // reading by line allows flipping, and avoids stride-related trouble
for (int y = 0; y < h; y++) { int y1 = dir > 0 ? 0 : h;
gl->ReadPixels(0, h - y - 1, w, 1, GL_RGB, GL_UNSIGNED_BYTE, for (int y = 0; y < h; y++)
image->planes[0] + y * image->stride[0]); gl->ReadPixels(0, y, w, 1, format, type, dst + (y1 + dir * y) * dst_stride);
}
gl->PixelStorei(GL_PACK_ALIGNMENT, 4); gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
gl->BindFramebuffer(GL_FRAMEBUFFER, 0); gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
return image; return true;
} }
static void gl_vao_enable_attribs(struct gl_vao *vao) static void gl_vao_enable_attribs(struct gl_vao *vao)

View File

@ -32,7 +32,8 @@ void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
const void *dataptr, int stride, const void *dataptr, int stride,
int x, int y, int w, int h); int x, int y, int w, int h);
mp_image_t *gl_read_fbo_contents(GL *gl, int fbo, int w, int h); bool gl_read_fbo_contents(GL *gl, int fbo, int dir, GLenum format, GLenum type,
int w, int h, uint8_t *dst, int dst_stride);
struct gl_vao { struct gl_vao {
GL *gl; GL *gl;

View File

@ -1324,6 +1324,15 @@ struct mp_image *vo_get_current_frame(struct vo *vo)
return r; return r;
} }
struct vo_frame *vo_get_current_vo_frame(struct vo *vo)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
struct vo_frame *r = vo_frame_ref(vo->in->current_frame);
pthread_mutex_unlock(&in->lock);
return r;
}
static void destroy_frame(void *p) static void destroy_frame(void *p)
{ {
struct vo_frame *frame = p; struct vo_frame *frame = p;

View File

@ -102,8 +102,13 @@ enum mp_voctrl {
VOCTRL_GET_DISPLAY_NAMES, VOCTRL_GET_DISPLAY_NAMES,
// Retrieve window contents. (Normal screenshots use vo_get_current_frame().) // Retrieve window contents. (Normal screenshots use vo_get_current_frame().)
// Deprecated for VOCTRL_SCREENSHOT with corresponding flags.
VOCTRL_SCREENSHOT_WIN, // struct mp_image** VOCTRL_SCREENSHOT_WIN, // struct mp_image**
// A normal screenshot - VOs can react to this if vo_get_current_frame() is
// not sufficient.
VOCTRL_SCREENSHOT, // struct voctrl_screenshot*
VOCTRL_UPDATE_RENDER_OPTS, VOCTRL_UPDATE_RENDER_OPTS,
VOCTRL_GET_ICC_PROFILE, // bstr* VOCTRL_GET_ICC_PROFILE, // bstr*
@ -170,6 +175,11 @@ struct voctrl_performance_data {
struct mp_frame_perf fresh, redraw; struct mp_frame_perf fresh, redraw;
}; };
struct voctrl_screenshot {
bool scaled, subs, osd, high_bit_depth;
struct mp_image *res;
};
enum { enum {
// VO does handle mp_image_params.rotate in 90 degree steps // VO does handle mp_image_params.rotate in 90 degree steps
VO_CAP_ROTATE90 = 1 << 0, VO_CAP_ROTATE90 = 1 << 0,
@ -447,6 +457,7 @@ double vo_get_estimated_vsync_jitter(struct vo *vo);
double vo_get_display_fps(struct vo *vo); double vo_get_display_fps(struct vo *vo);
double vo_get_delay(struct vo *vo); double vo_get_delay(struct vo *vo);
void vo_discard_timing_info(struct vo *vo); void vo_discard_timing_info(struct vo *vo);
struct vo_frame *vo_get_current_vo_frame(struct vo *vo);
struct mp_image *vo_get_image(struct vo *vo, int imgfmt, int w, int h, struct mp_image *vo_get_image(struct vo *vo, int imgfmt, int w, int h,
int stride_align); int stride_align);

View File

@ -188,6 +188,20 @@ static int control(struct vo *vo, uint32_t request, void *data)
*(struct mp_image **)data = screen; *(struct mp_image **)data = screen;
return true; return true;
} }
case VOCTRL_SCREENSHOT: {
struct vo_frame *frame = vo_get_current_vo_frame(vo);
if (frame) {
// Disable interpolation and such.
frame->redraw = true;
frame->repeat = false;
frame->still = true;
frame->pts = 0;
frame->duration = -1;
gl_video_screenshot(p->renderer, frame, data);
}
talloc_free(frame);
return true;
}
case VOCTRL_LOAD_HWDEC_API: case VOCTRL_LOAD_HWDEC_API:
request_hwdec_api(vo); request_hwdec_api(vo);
return true; return true;