From 74581a61064f56b170e555fa72d9cdca161d2307 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 22 Jan 2015 17:47:14 +0100 Subject: video: handle hwdec screenshots differently Instead of converting the hw surface to an image in the VO, provide a generic way to convet hw surfaces, and use this in the screenshot code. It's all relatively straightforward, except vdpau is being terrible. It needs a huge chunk of new code, because copying back is not simple. --- player/screenshot.c | 10 ++++++ video/hwdec.h | 9 ++++++ video/mp_image.c | 1 + video/out/gl_hwdec.h | 4 +-- video/out/gl_hwdec_vda.c | 57 ++++++++++++++++++--------------- video/out/gl_video.c | 16 ++++------ video/out/vo_vaapi.c | 9 +----- video/out/vo_vdpau.c | 52 ++++-------------------------- video/vaapi.c | 12 +++++-- video/vdpau.c | 81 +++++++++++++++++++++++++++++++++++++++++++++-- video/vdpau.h | 10 ++++-- video/vdpau_functions.inc | 2 ++ video/vdpau_mixer.c | 16 +++++++++- 13 files changed, 182 insertions(+), 97 deletions(-) diff --git a/player/screenshot.c b/player/screenshot.c index 4e22afc69d..7d1c743e40 100644 --- a/player/screenshot.c +++ b/player/screenshot.c @@ -343,6 +343,16 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode) image = args.out_image; if (image) { + if (mpctx->d_video && mpctx->d_video->hwdec_info) { + struct mp_hwdec_ctx *ctx = mpctx->d_video->hwdec_info->hwctx; + struct mp_image *nimage = NULL; + if (ctx && ctx->download_image) + nimage = ctx->download_image(ctx, image, NULL); + if (nimage) { + talloc_free(image); + image = nimage; + } + } if (mode == MODE_SUBTITLES && !args.has_osd) add_subs(mpctx, image); } diff --git a/video/hwdec.h b/video/hwdec.h index 149cd81bae..a7d2cf8c4b 100644 --- a/video/hwdec.h +++ b/video/hwdec.h @@ -9,6 +9,15 @@ struct mp_hwdec_ctx { // API-specific, not needed by all backends. struct mp_vdpau_ctx *vdpau_ctx; struct mp_vaapi_ctx *vaapi_ctx; + + // Optional. + // Allocates a software image from the pool, downloads the hw image from + // mpi, and returns it. + // pool can be NULL (then just use straight allocation). + // Return NULL on error or if mpi has the wrong format. + struct mp_image *(*download_image)(struct mp_hwdec_ctx *ctx, + struct mp_image *mpi, + struct mp_image_pool *swpool); }; // Used to communicate hardware decoder API handles from VO to video decoder. diff --git a/video/mp_image.c b/video/mp_image.c index 95ed5ae08a..cf17429d89 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -372,6 +372,7 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src) dst->params.chroma_location = src->params.chroma_location; dst->params.outputlevels = src->params.outputlevels; } + mp_image_params_guess_csp(&dst->params); // ensure colorspace consistency if ((dst->fmt.flags & MP_IMGFLAG_PAL) && (src->fmt.flags & MP_IMGFLAG_PAL)) { if (dst->planes[1] && src->planes[1]) memcpy(dst->planes[1], src->planes[1], MP_PALETTE_SIZE); diff --git a/video/out/gl_hwdec.h b/video/out/gl_hwdec.h index 8d2f563d5e..ffe685fa97 100644 --- a/video/out/gl_hwdec.h +++ b/video/out/gl_hwdec.h @@ -43,9 +43,7 @@ struct gl_hwdec_driver { // Undo map_image(). The user of map_image() calls this when the textures // are not needed anymore. void (*unmap_image)(struct gl_hwdec *hw); - // Return a mp_image downloaded from the GPU (optional) - struct mp_image *(*download_image)(struct gl_hwdec *hw, - struct mp_image *hw_image); + void (*destroy)(struct gl_hwdec *hw); }; diff --git a/video/out/gl_hwdec_vda.c b/video/out/gl_hwdec_vda.c index 4264dff975..d90b3419ae 100644 --- a/video/out/gl_hwdec_vda.c +++ b/video/out/gl_hwdec_vda.c @@ -22,14 +22,42 @@ #include #include -#include "video/decode/dec_video.h" +#include "video/mp_image_pool.h" #include "gl_hwdec.h" struct priv { CVPixelBufferRef pbuf; GLuint gl_texture; + struct mp_hwdec_ctx hwctx; }; +static struct mp_image *download_image(struct mp_hwdec_ctx *ctx, + struct mp_image *hw_image, + struct mp_image_pool *swpool) +{ + if (hw_image->imgfmt != IMGFMT_VDA) + return NULL; + + CVPixelBufferRef pbuf = (CVPixelBufferRef)hw_image->planes[3]; + CVPixelBufferLockBaseAddress(pbuf, 0); + void *base = CVPixelBufferGetBaseAddress(pbuf); + size_t width = CVPixelBufferGetWidth(pbuf); + size_t height = CVPixelBufferGetHeight(pbuf); + size_t stride = CVPixelBufferGetBytesPerRow(pbuf); + + struct mp_image img = {0}; + mp_image_setfmt(&img, IMGFMT_UYVY); + mp_image_set_size(&img, width, height); + img.planes[0] = base; + img.stride[0] = stride; + mp_image_copy_attributes(&img, hw_image); + + struct mp_image *image = mp_image_pool_new_copy(swpool, &img); + CVPixelBufferUnlockBaseAddress(pbuf, 0); + + return image; +} + static bool check_hwdec(struct gl_hwdec *hw) { if (hw->gl_texture_target != GL_TEXTURE_RECTANGLE) { @@ -60,6 +88,9 @@ static int create(struct gl_hwdec *hw) if (!check_hwdec(hw)) return -1; + hw->hwctx = &p->hwctx; + hw->hwctx->download_image = download_image; + GL *gl = hw->gl; gl->GenTextures(1, &p->gl_texture); @@ -104,28 +135,6 @@ static int map_image(struct gl_hwdec *hw, struct mp_image *hw_image, static void unmap_image(struct gl_hwdec *hw) { } -static struct mp_image *download_image(struct gl_hwdec *hw, - struct mp_image *hw_image) -{ - CVPixelBufferRef pbuf = (CVPixelBufferRef)hw_image->planes[3]; - CVPixelBufferLockBaseAddress(pbuf, 0); - void *base = CVPixelBufferGetBaseAddress(pbuf); - size_t width = CVPixelBufferGetWidth(pbuf); - size_t height = CVPixelBufferGetHeight(pbuf); - size_t stride = CVPixelBufferGetBytesPerRow(pbuf); - - struct mp_image img = {0}; - mp_image_setfmt(&img, IMGFMT_UYVY); - mp_image_set_size(&img, width, height); - img.planes[0] = base; - img.stride[0] = stride; - - struct mp_image *image = mp_image_new_copy(&img); - CVPixelBufferUnlockBaseAddress(pbuf, 0); - - return image; -} - static void destroy(struct gl_hwdec *hw) { struct priv *p = hw->priv; @@ -136,7 +145,6 @@ static void destroy(struct gl_hwdec *hw) p->gl_texture = 0; } - const struct gl_hwdec_driver gl_hwdec_vda = { .api_name = "vda", .imgfmt = IMGFMT_VDA, @@ -144,6 +152,5 @@ const struct gl_hwdec_driver gl_hwdec_vda = { .reinit = reinit, .map_image = map_image, .unmap_image = unmap_image, - .download_image = download_image, .destroy = destroy, }; diff --git a/video/out/gl_video.c b/video/out/gl_video.c index e3eb4d1abf..1f32844c07 100644 --- a/video/out/gl_video.c +++ b/video/out/gl_video.c @@ -1671,7 +1671,7 @@ static void uninit_video(struct gl_video *p) fbotex_uninit(p, &p->indirect_fbo); fbotex_uninit(p, &p->scale_sep_fbo); - + // Invalidate image_params to ensure that gl_video_config() will call // init_video() on uninitialized gl_video. p->image_params = (struct mp_image_params){0}; @@ -2017,16 +2017,14 @@ struct mp_image *gl_video_download_image(struct gl_video *p) struct video_image *vimg = &p->image; - if (!p->have_image || !gl->GetTexImage) + if (!p->have_image) return NULL; - if (p->hwdec_active && p->hwdec->driver->download_image) { - struct mp_image *dlimage = - p->hwdec->driver->download_image(p->hwdec, vimg->hwimage); - if (dlimage) - mp_image_set_attributes(dlimage, &p->image_params); - return dlimage; - } + if (p->hwdec_active) + return mp_image_new_ref(vimg->hwimage); + + if (!gl->GetTexImage) + return NULL; set_image_textures(p, vimg, NULL); diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c index cf07787a48..06c7b3c9f4 100644 --- a/video/out/vo_vaapi.c +++ b/video/out/vo_vaapi.c @@ -303,14 +303,7 @@ static struct mp_image *get_screenshot(struct priv *p) struct mp_image *hwimg = p->output_surfaces[p->visible_surface]; if (!hwimg) return NULL; - struct mp_image *img = va_surface_download(hwimg, NULL); - if (!img) - return NULL; - struct mp_image_params params = p->image_params; - params.imgfmt = img->imgfmt; - mp_image_params_guess_csp(¶ms); // ensure colorspace consistency - mp_image_set_params(img, ¶ms); - return img; + return mp_image_new_ref(hwimg); } static void free_subpicture(struct priv *p, struct vaapi_osd_image *img) diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c index a022946c1b..e89cda6490 100644 --- a/video/out/vo_vdpau.c +++ b/video/out/vo_vdpau.c @@ -80,7 +80,6 @@ struct vdpctx { VdpPresentationQueue flip_queue; VdpOutputSurface output_surfaces[MAX_OUTPUT_SURFACES]; - VdpOutputSurface screenshot_surface; int num_output_surfaces; VdpOutputSurface black_pixel; @@ -176,24 +175,16 @@ static int render_video_to_output_surface(struct vo *vo, "vdp_presentation_queue_block_until_surface_idle"); if (vc->rgb_mode) { - VdpOutputSurface surface = (uintptr_t)mpi->planes[3]; + // Clear the borders between video and window (if there are any). + // For some reason, video_mixer_render doesn't need it for YUV. int flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_0; vdp_st = vdp->output_surface_render_output_surface(output_surface, NULL, vc->black_pixel, NULL, NULL, NULL, flags); CHECK_VDP_WARNING(vo, "Error clearing screen"); - vdp_st = vdp->output_surface_render_output_surface(output_surface, - output_rect, - surface, - video_rect, - NULL, NULL, flags); - CHECK_VDP_WARNING(vo, "Error when calling " - "vdp_output_surface_render_output_surface"); - return 0; } - struct mp_vdpau_mixer_frame *frame = mp_vdpau_mixed_frame_get(mpi); struct mp_vdpau_mixer_opts opts = {0}; if (frame) @@ -344,12 +335,6 @@ static void free_video_specific(struct vo *vo) forget_frames(vo, false); - if (vc->screenshot_surface != VDP_INVALID_HANDLE) { - vdp_st = vdp->output_surface_destroy(vc->screenshot_surface); - CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_destroy"); - } - vc->screenshot_surface = VDP_INVALID_HANDLE; - if (vc->black_pixel != VDP_INVALID_HANDLE) { vdp_st = vdp->output_surface_destroy(vc->black_pixel); CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_destroy"); @@ -399,7 +384,6 @@ static void mark_vdpau_objects_uninitialized(struct vo *vo) vc->flip_target = VDP_INVALID_HANDLE; for (int i = 0; i < MAX_OUTPUT_SURFACES; i++) vc->output_surfaces[i] = VDP_INVALID_HANDLE; - vc->screenshot_surface = VDP_INVALID_HANDLE; vc->vdp_device = VDP_INVALID_HANDLE; for (int i = 0; i < MAX_OSD_PARTS; i++) { struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i]; @@ -878,30 +862,6 @@ static struct mp_image *read_output_surface(struct vo *vo, return image; } -static struct mp_image *get_screenshot(struct vo *vo) -{ - struct vdpctx *vc = vo->priv; - VdpStatus vdp_st; - struct vdp_functions *vdp = vc->vdp; - - if (!vo->params) - return NULL; - - if (vc->screenshot_surface == VDP_INVALID_HANDLE) { - vdp_st = vdp->output_surface_create(vc->vdp_device, - OUTPUT_RGBA_FORMAT, - vo->params->d_w, vo->params->d_h, - &vc->screenshot_surface); - CHECK_VDP_WARNING(vo, "Error when calling vdp_output_surface_create"); - } - - VdpRect in = { .x1 = vo->params->w, .y1 = vo->params->h }; - VdpRect out = { .x1 = vo->params->d_w, .y1 = vo->params->d_h }; - render_video_to_output_surface(vo, vc->screenshot_surface, &out, &in); - - return read_output_surface(vo, vc->screenshot_surface, out.x1, out.y1); -} - static struct mp_image *get_window_screenshot(struct vo *vo) { struct vdpctx *vc = vo->priv; @@ -1092,10 +1052,12 @@ static int control(struct vo *vo, uint32_t request, void *data) if (!status_ok(vo)) return false; struct voctrl_screenshot_args *args = data; - if (args->full_window) + if (args->full_window) { args->out_image = get_window_screenshot(vo); - else - args->out_image = get_screenshot(vo); + } else { + args->out_image = + vc->current_image ? mp_image_new_ref(vc->current_image) : NULL; + } return true; } case VOCTRL_GET_PREF_DEINT: diff --git a/video/vaapi.c b/video/vaapi.c index 13e78f0f31..54d75e775a 100644 --- a/video/vaapi.c +++ b/video/vaapi.c @@ -84,6 +84,13 @@ uint32_t va_fourcc_from_imgfmt(int imgfmt) return 0; } +static struct mp_image *ctx_download_image(struct mp_hwdec_ctx *ctx, + struct mp_image *mpi, + struct mp_image_pool *swpool) +{ + return va_surface_download(mpi, swpool); +} + struct va_image_formats { VAImageFormat *entries; int num; @@ -125,6 +132,7 @@ struct mp_vaapi_ctx *va_initialize(VADisplay *display, struct mp_log *plog) .hwctx = { .priv = res, .vaapi_ctx = res, + .download_image = ctx_download_image, }, }; mpthread_mutex_init_recursive(&res->lock); @@ -405,12 +413,12 @@ static struct mp_image *try_download(struct mp_image *src, struct mp_image *dst = NULL; struct mp_image tmp; if (va_image_map(p->ctx, image, &tmp)) { - dst = pool ? mp_image_pool_get(pool, tmp.imgfmt, tmp.w, tmp.h) - : mp_image_alloc(tmp.imgfmt, tmp.w, tmp.h); + dst = mp_image_pool_get(pool, tmp.imgfmt, tmp.w, tmp.h); if (dst) mp_image_copy(dst, &tmp); va_image_unmap(p->ctx, image); } + mp_image_copy_attributes(dst, src); return dst; } diff --git a/video/vdpau.c b/video/vdpau.c index 3f7488470c..e1daf327af 100644 --- a/video/vdpau.c +++ b/video/vdpau.c @@ -23,8 +23,76 @@ #include "osdep/timer.h" #include "video/out/x11_common.h" -#include "video/img_format.h" -#include "video/mp_image.h" +#include "img_format.h" +#include "mp_image.h" +#include "mp_image_pool.h" +#include "vdpau_mixer.h" + +static struct mp_image *download_image(struct mp_hwdec_ctx *hwctx, + struct mp_image *mpi, + struct mp_image_pool *swpool) +{ + struct mp_vdpau_ctx *ctx = hwctx->vdpau_ctx; + struct vdp_functions *vdp = &ctx->vdp; + VdpStatus vdp_st; + + struct mp_image *res = NULL; + int w = mpi->params.d_w; + int h = mpi->params.d_h; + + // Abuse this lock for our own purposes. It could use its own lock instead. + pthread_mutex_lock(&ctx->pool_lock); + + if (ctx->getimg_surface == VDP_INVALID_HANDLE || + ctx->getimg_w < w || ctx->getimg_h < h) + { + if (ctx->getimg_surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_destroy(ctx->getimg_surface); + CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy"); + } + ctx->getimg_surface = VDP_INVALID_HANDLE; + vdp_st = vdp->output_surface_create(ctx->vdp_device, + VDP_RGBA_FORMAT_B8G8R8A8, w, h, + &ctx->getimg_surface); + CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_create"); + if (vdp_st != VDP_STATUS_OK) + goto error; + ctx->getimg_w = w; + ctx->getimg_h = h; + } + + if (!ctx->getimg_mixer) + ctx->getimg_mixer = mp_vdpau_mixer_create(ctx, ctx->log); + + VdpRect in = { .x1 = mpi->w, .y1 = mpi->h }; + VdpRect out = { .x1 = w, .y1 = h }; + if (mp_vdpau_mixer_render(ctx->getimg_mixer, NULL, ctx->getimg_surface, &out, + mpi, &in) < 0) + goto error; + + res = mp_image_pool_get(swpool, IMGFMT_BGR32, ctx->getimg_w, ctx->getimg_h); + if (!res) + goto error; + + void *dst_planes[] = { res->planes[0] }; + uint32_t dst_pitches[] = { res->stride[0] }; + vdp_st = vdp->output_surface_get_bits_native(ctx->getimg_surface, NULL, + dst_planes, dst_pitches); + CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_get_bits_native"); + if (vdp_st != VDP_STATUS_OK) + goto error; + + mp_image_set_size(res, w, h); + mp_image_copy_attributes(res, mpi); + + pthread_mutex_unlock(&ctx->pool_lock); + return res; +error: + talloc_free(res); + MP_WARN(ctx, "Error copying image from GPU.\n"); + pthread_mutex_unlock(&ctx->pool_lock); + return NULL; +} static void mark_vdpau_objects_uninitialized(struct mp_vdpau_ctx *ctx) { @@ -305,7 +373,9 @@ struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11 .hwctx = { .priv = ctx, .vdpau_ctx = ctx, + .download_image = download_image, }, + .getimg_surface = VDP_INVALID_HANDLE, }; mpthread_mutex_init_recursive(&ctx->preempt_lock); pthread_mutex_init(&ctx->pool_lock, NULL); @@ -337,6 +407,13 @@ void mp_vdpau_destroy(struct mp_vdpau_ctx *ctx) } } + if (ctx->getimg_mixer) + mp_vdpau_mixer_destroy(ctx->getimg_mixer); + if (ctx->getimg_surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_destroy(ctx->getimg_surface); + CHECK_VDP_WARNING(ctx, "Error when calling vdp_output_surface_destroy"); + } + if (vdp->device_destroy && ctx->vdp_device != VDP_INVALID_HANDLE) { vdp_st = vdp->device_destroy(ctx->vdp_device); CHECK_VDP_WARNING(ctx, "Error when calling vdp_device_destroy"); diff --git a/video/vdpau.h b/video/vdpau.h index 0af00f098b..2304ecd0d6 100644 --- a/video/vdpau.h +++ b/video/vdpau.h @@ -12,14 +12,17 @@ #include "common/msg.h" #include "hwdec.h" -#define CHECK_VDP_ERROR(ctx, message) \ +#define CHECK_VDP_ERROR_ST(ctx, message, statement) \ do { \ if (vdp_st != VDP_STATUS_OK) { \ MP_ERR(ctx, "%s: %s\n", message, vdp->get_error_string(vdp_st)); \ - return -1; \ + statement \ } \ } while (0) +#define CHECK_VDP_ERROR(ctx, message) \ + CHECK_VDP_ERROR_ST(ctx, message, return -1;) + #define CHECK_VDP_WARNING(ctx, message) \ do { \ if (vdp_st != VDP_STATUS_OK) \ @@ -71,6 +74,9 @@ struct mp_vdpau_ctx { bool in_use; int64_t age; } video_surfaces[MAX_VIDEO_SURFACES]; + struct mp_vdpau_mixer *getimg_mixer; + VdpOutputSurface getimg_surface; + int getimg_w, getimg_h; }; struct mp_vdpau_ctx *mp_vdpau_create_device_x11(struct mp_log *log, Display *x11); diff --git a/video/vdpau_functions.inc b/video/vdpau_functions.inc index 5604420f69..001a0e9906 100644 --- a/video/vdpau_functions.inc +++ b/video/vdpau_functions.inc @@ -43,3 +43,5 @@ VDP_FUNCTION(VdpVideoMixerSetFeatureEnables, VDP_FUNC_ID_VIDEO_MIXER_SET_FEATURE VDP_FUNCTION(VdpVideoSurfaceCreate, VDP_FUNC_ID_VIDEO_SURFACE_CREATE, video_surface_create) VDP_FUNCTION(VdpVideoSurfaceDestroy, VDP_FUNC_ID_VIDEO_SURFACE_DESTROY, video_surface_destroy) VDP_FUNCTION(VdpVideoSurfacePutBitsYCbCr, VDP_FUNC_ID_VIDEO_SURFACE_PUT_BITS_Y_CB_CR, video_surface_put_bits_y_cb_cr) +VDP_FUNCTION(VdpVideoSurfaceGetBitsYCbCr, VDP_FUNC_ID_VIDEO_SURFACE_GET_BITS_Y_CB_CR, video_surface_get_bits_y_cb_cr) +VDP_FUNCTION(VdpVideoSurfaceGetParameters, VDP_FUNC_ID_VIDEO_SURFACE_GET_PARAMETERS, video_surface_get_parameters) diff --git a/video/vdpau_mixer.c b/video/vdpau_mixer.c index d3a4a02457..3c8f3fb908 100644 --- a/video/vdpau_mixer.c +++ b/video/vdpau_mixer.c @@ -221,7 +221,21 @@ int mp_vdpau_mixer_render(struct mp_vdpau_mixer *mixer, struct vdp_functions *vdp = &mixer->ctx->vdp; VdpStatus vdp_st; - assert(video->imgfmt == IMGFMT_VDPAU); + if (video->imgfmt == IMGFMT_VDPAU_OUTPUT) { + VdpOutputSurface surface = (uintptr_t)video->planes[3]; + int flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_0; + vdp_st = vdp->output_surface_render_output_surface(output, + output_rect, + surface, + video_rect, + NULL, NULL, flags); + CHECK_VDP_WARNING(mixer, "Error when calling " + "vdp_output_surface_render_output_surface"); + return 0; + } + + if (video->imgfmt != IMGFMT_VDPAU) + return -1; struct mp_vdpau_mixer_frame *frame = mp_vdpau_mixed_frame_get(video); struct mp_vdpau_mixer_frame fallback = {{0}}; -- cgit v1.2.3