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.
This commit is contained in:
wm4 2015-01-22 17:47:14 +01:00
parent e9ac3fc3a1
commit 74581a6106
13 changed files with 182 additions and 97 deletions

View File

@ -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);
}

View File

@ -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.

View File

@ -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);

View File

@ -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);
};

View File

@ -22,14 +22,42 @@
#include <OpenGL/OpenGL.h>
#include <OpenGL/CGLIOSurface.h>
#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,
};

View File

@ -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);

View File

@ -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(&params); // ensure colorspace consistency
mp_image_set_params(img, &params);
return img;
return mp_image_new_ref(hwimg);
}
static void free_subpicture(struct priv *p, struct vaapi_osd_image *img)

View File

@ -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:

View File

@ -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;
}

View File

@ -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");

View File

@ -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);

View File

@ -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)

View File

@ -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}};