summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/options.rst3
-rw-r--r--player/screenshot.c31
-rw-r--r--video/fmt-conversion.c2
-rw-r--r--video/image_writer.c5
-rw-r--r--video/image_writer.h3
-rw-r--r--video/img_format.h3
-rw-r--r--video/out/gpu/ra.h12
-rw-r--r--video/out/gpu/video.c80
-rw-r--r--video/out/gpu/video.h4
-rw-r--r--video/out/opengl/context.c23
-rw-r--r--video/out/opengl/ra_gl.c27
-rw-r--r--video/out/opengl/utils.c22
-rw-r--r--video/out/opengl/utils.h3
-rw-r--r--video/out/vo.c9
-rw-r--r--video/out/vo.h11
-rw-r--r--video/out/vo_gpu.c14
16 files changed, 219 insertions, 33 deletions
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 44aff271d3..0ffd6c783a 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -3309,7 +3309,8 @@ Screenshot
``--screenshot-high-bit-depth=<yes|no>``
If possible, write screenshots with a bit depth similar to the source
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>``
Specify the filename template used to save screenshots. The template
diff --git a/player/screenshot.c b/player/screenshot.c
index 16ff357dc8..d0e5777a65 100644
--- a/player/screenshot.c
+++ b/player/screenshot.c
@@ -389,16 +389,30 @@ static void add_subs(struct MPContext *mpctx, struct mp_image *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;
if (mode == MODE_SUBTITLES && osd_get_render_subs_in_filter(mpctx->osd))
mode = 0;
+ bool need_add_subs = mode == MODE_SUBTITLES;
if (mpctx->video_out && mpctx->video_out->config_ok) {
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);
if (!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;
}
- if (image && mode == MODE_SUBTITLES)
+ if (image && need_add_subs)
add_subs(mpctx, 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 *mpi = screenshot_get(mpctx, mode);
+ struct mp_image *mpi = screenshot_get(mpctx, mode, false);
if (!mpi)
return NULL;
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);
if (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) {
screenshot_msg(ctx, MSGL_ERR, "Taking screenshot failed.");
goto end;
@@ -471,10 +486,12 @@ void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
ctx->mode = mode;
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) {
- struct image_writer_opts *opts = mpctx->opts->screenshot_image_opts;
char *filename = gen_fname(ctx, image_writer_file_ext(opts));
if (filename)
write_screenshot(mpctx, image, filename, NULL, async);
diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c
index f6a774e289..d803ac0aa1 100644
--- a/video/fmt-conversion.c
+++ b/video/fmt-conversion.c
@@ -57,6 +57,8 @@ static const struct {
{IMGFMT_0BGR, AV_PIX_FMT_ABGR},
#endif
+ {IMGFMT_RGBA64, AV_PIX_FMT_RGBA64},
+
{IMGFMT_VDPAU, AV_PIX_FMT_VDPAU},
#if HAVE_VIDEOTOOLBOX_HWACCEL
{IMGFMT_VIDEOTOOLBOX, AV_PIX_FMT_VIDEOTOOLBOX},
diff --git a/video/image_writer.c b/video/image_writer.c
index 6fc933e34d..6a092aa17f 100644
--- a/video/image_writer.c
+++ b/video/image_writer.c
@@ -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);
}
+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)
{
for (int n = 0; mp_image_writer_formats[n].name; n++) {
diff --git a/video/image_writer.h b/video/image_writer.h
index 7f7f123499..98cb95a898 100644
--- a/video/image_writer.h
+++ b/video/image_writer.h
@@ -42,6 +42,9 @@ extern const struct m_option image_writer_opts[];
// Return the file extension that will be used, e.g. "png".
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.
int image_writer_format_from_ext(const char *ext);
diff --git a/video/img_format.h b/video/img_format.h
index b79cbdb99b..d911e3e2ab 100644
--- a/video/img_format.h
+++ b/video/img_format.h
@@ -182,6 +182,9 @@ enum mp_imgfmt {
IMGFMT_RGB0_START = IMGFMT_0RGB,
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
IMGFMT_RGB565, // 5r 6g 5b (MSB to LSB)
diff --git a/video/out/gpu/ra.h b/video/out/gpu/ra.h
index f1037e45c3..05a7c54a66 100644
--- a/video/out/gpu/ra.h
+++ b/video/out/gpu/ra.h
@@ -110,6 +110,7 @@ struct ra_tex_params {
bool blit_src; // must be usable as a blit source
bool blit_dst; // must be usable as a blit destination
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.
bool src_linear; // if false, use nearest sampling (whether this can
// 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!)
};
+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
// on a buffer.
enum ra_buf_type {
@@ -379,6 +387,10 @@ struct ra_fns {
// Returns whether successful.
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,
// a uniform buffer, a shader storage buffer or possibly others.
// Not all usage types must be supported; may return NULL if unavailable.
diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c
index dcacdb1d01..6edf4b0ccf 100644
--- a/video/out/gpu/video.c
+++ b/video/out/gpu/video.c
@@ -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
// flags: bit set of RENDER_FRAME_* flags
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;
@@ -3156,6 +3156,84 @@ done:
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.
void gl_video_set_clear_color(struct gl_video *p, struct m_color c)
{
diff --git a/video/out/gpu/video.h b/video/out/gpu/video.h
index cd17308810..80f31c2934 100644
--- a/video/out/gpu/video.h
+++ b/video/out/gpu/video.h
@@ -146,6 +146,7 @@ extern const struct m_sub_options gl_video_conf;
struct gl_video;
struct vo_frame;
+struct voctrl_screenshot;
enum {
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,
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 rmin, float rmax, float lux);
void gl_video_set_ambient_lux(struct gl_video *p, int lux);
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
index cdaf6320cd..8f44119210 100644
--- a/video/out/opengl/context.c
+++ b/video/out/opengl/context.c
@@ -251,16 +251,21 @@ struct mp_image *ra_gl_ctx_screenshot(struct ra_swapchain *sw)
{
struct priv *p = sw->priv;
+ struct mp_image *screen = mp_image_alloc(IMGFMT_RGB24, p->wrapped_fb->params.w,
+ p->wrapped_fb->params.h);
+ if (!screen)
+ return NULL;
+
+ int dir = p->params.flipped ? 1 : -1;
+
assert(p->wrapped_fb);
- struct mp_image *screen = gl_read_fbo_contents(p->gl, p->main_fb,
- p->wrapped_fb->params.w,
- p->wrapped_fb->params.h);
-
- // OpenGL FB is also read in flipped order, so we need to flip when the
- // rendering is *not* flipped, which in our case is whenever
- // p->params.flipped is true. I hope that made sense
- if (screen && p->params.flipped)
- mp_image_vflip(screen);
+ if (!gl_read_fbo_contents(p->gl, p->main_fb, dir, GL_RGB, GL_UNSIGNED_BYTE,
+ p->wrapped_fb->params.w, p->wrapped_fb->params.h,
+ screen->planes[0], screen->stride[0]))
+ {
+ talloc_free(screen);
+ return NULL;
+ }
return screen;
}
diff --git a/video/out/opengl/ra_gl.c b/video/out/opengl/ra_gl.c
index 356ed81d03..7112464e87 100644
--- a/video/out/opengl/ra_gl.c
+++ b/video/out/opengl/ra_gl.c
@@ -277,6 +277,13 @@ static struct ra_tex *gl_tex_create_blank(struct ra *ra,
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;
}
@@ -329,8 +336,11 @@ static struct ra_tex *gl_tex_create(struct ra *ra,
gl_check_error(gl, ra->log, "after creating texture");
- // Even blitting needs an FBO in OpenGL for strange reasons
- if (tex->params.render_dst || tex->params.blit_src || tex->params.blit_dst) {
+ // Even blitting needs an FBO in OpenGL for strange reasons.
+ // 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) {
MP_ERR(ra, "Trying to create renderable texture with unsupported "
"format.\n");
@@ -512,6 +522,18 @@ static bool gl_tex_upload(struct ra *ra,
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)
{
if (!buf)
@@ -1134,6 +1156,7 @@ static struct ra_fns ra_fns_gl = {
.tex_create = gl_tex_create,
.tex_destroy = gl_tex_destroy,
.tex_upload = gl_tex_upload,
+ .tex_download = gl_tex_download,
.buf_create = gl_buf_create,
.buf_destroy = gl_buf_destroy,
.buf_update = gl_buf_update,
diff --git a/video/out/opengl/utils.c b/video/out/opengl/utils.c
index 34f4736705..a551ce4299 100644
--- a/video/out/opengl/utils.c
+++ b/video/out/opengl/utils.c
@@ -105,25 +105,23 @@ void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
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)
- return NULL; // ES can't read from front buffer
- mp_image_t *image = mp_image_alloc(IMGFMT_RGB24, w, h);
- if (!image)
- return NULL;
+ assert(dir == 1 || dir == -1);
+ if (fbo == 0 && gl->es)
+ return false; // ES can't read from front buffer
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
GLenum obj = fbo ? GL_COLOR_ATTACHMENT0 : GL_FRONT;
gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
gl->ReadBuffer(obj);
- //flip image while reading (and also avoid stride-related trouble)
- for (int y = 0; y < h; y++) {
- gl->ReadPixels(0, h - y - 1, w, 1, GL_RGB, GL_UNSIGNED_BYTE,
- image->planes[0] + y * image->stride[0]);
- }
+ // reading by line allows flipping, and avoids stride-related trouble
+ int y1 = dir > 0 ? 0 : h;
+ for (int y = 0; y < h; y++)
+ gl->ReadPixels(0, y, w, 1, format, type, dst + (y1 + dir * y) * dst_stride);
gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
- return image;
+ return true;
}
static void gl_vao_enable_attribs(struct gl_vao *vao)
diff --git a/video/out/opengl/utils.h b/video/out/opengl/utils.h
index 53127e479e..9bcadae91c 100644
--- a/video/out/opengl/utils.h
+++ b/video/out/opengl/utils.h
@@ -32,7 +32,8 @@ void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
const void *dataptr, int stride,
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 {
GL *gl;
diff --git a/video/out/vo.c b/video/out/vo.c
index 562c6b048a..c999138eee 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -1324,6 +1324,15 @@ struct mp_image *vo_get_current_frame(struct vo *vo)
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)
{
struct vo_frame *frame = p;
diff --git a/video/out/vo.h b/video/out/vo.h
index bf5995f722..4ce2cf1371 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -102,8 +102,13 @@ enum mp_voctrl {
VOCTRL_GET_DISPLAY_NAMES,
// Retrieve window contents. (Normal screenshots use vo_get_current_frame().)
+ // Deprecated for VOCTRL_SCREENSHOT with corresponding flags.
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_GET_ICC_PROFILE, // bstr*
@@ -170,6 +175,11 @@ struct voctrl_performance_data {
struct mp_frame_perf fresh, redraw;
};
+struct voctrl_screenshot {
+ bool scaled, subs, osd, high_bit_depth;
+ struct mp_image *res;
+};
+
enum {
// VO does handle mp_image_params.rotate in 90 degree steps
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_delay(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,
int stride_align);
diff --git a/video/out/vo_gpu.c b/video/out/vo_gpu.c
index d3b27ca5bc..70c874fc54 100644
--- a/video/out/vo_gpu.c
+++ b/video/out/vo_gpu.c
@@ -188,6 +188,20 @@ static int control(struct vo *vo, uint32_t request, void *data)
*(struct mp_image **)data = screen;
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:
request_hwdec_api(vo);
return true;