diff options
-rw-r--r-- | DOCS/man/options.rst | 3 | ||||
-rw-r--r-- | player/screenshot.c | 31 | ||||
-rw-r--r-- | video/fmt-conversion.c | 2 | ||||
-rw-r--r-- | video/image_writer.c | 5 | ||||
-rw-r--r-- | video/image_writer.h | 3 | ||||
-rw-r--r-- | video/img_format.h | 3 | ||||
-rw-r--r-- | video/out/gpu/ra.h | 12 | ||||
-rw-r--r-- | video/out/gpu/video.c | 80 | ||||
-rw-r--r-- | video/out/gpu/video.h | 4 | ||||
-rw-r--r-- | video/out/opengl/context.c | 23 | ||||
-rw-r--r-- | video/out/opengl/ra_gl.c | 27 | ||||
-rw-r--r-- | video/out/opengl/utils.c | 22 | ||||
-rw-r--r-- | video/out/opengl/utils.h | 3 | ||||
-rw-r--r-- | video/out/vo.c | 9 | ||||
-rw-r--r-- | video/out/vo.h | 11 | ||||
-rw-r--r-- | video/out/vo_gpu.c | 14 |
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, ¶ms); + 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; |